简介:这份微信跑步小程序源码面向希望入门或进阶微信小程序开发的开发者,尤其适合想通过完整案例理解健康运动类应用实现思路的学习者。源码围绕跑步追踪与数据记录场景,覆盖页面结构与路由管理、数据模型与状态管理、事件处理、地理位置与计步器 API 调用、动画效果、网络请求、UI 组件与自定义组件、权限管理以及调试发布流程等关键知识点,可作为从零构建功能完备小程序的参考范本。资源包共 20 个文件,以 js 逻辑脚本、wxss 样式表、wxml 结构文件和 json 配置为主,另含少量图片与说明文档,压缩包约 12KB,体量轻便、便于快速导入微信开发者工具阅读与调试。目前已有 1975 人学习下载,说明其在同类案例中具备一定参考价值。通过梳理目录模块与代码组织方式,读者可对照理解实际项目的设计思路与编程技巧,提升小程序开发能力。
1. 从一份跑步小程序源码说起:它到底能跑出什么
去年帮一个做校园运动社群的团队看代码,他们手里有一套微信跑步小程序源码,想改成带轨迹记录和排行榜的版本。打开压缩包第一眼,目录结构比想象中规整:页面、组件、工具函数、云函数分层清楚,不是那种把所有逻辑塞进一个 Page 里的野生代码。这套源码解决的核心问题很具体——把「打开小程序、开始跑步、记录轨迹、结束保存、查看历史」这条链路完整跑通,同时把微信登录、位置授权、地图组件这些容易翻车的地方都处理过一遍。适合两类人:一是想快速拥有一个能上线的运动类小程序原型的开发者,二是拿它当微信小程序工程范本、学习页面与云开发如何协作的初学者。下面按「能干什么、怎么跑、怎么改」的顺序拆。
2. 工程结构与技术选型:页面、组件、云函数三层怎么分工
2.1 目录骨架与各层职责
拿到源码先别急着点运行,花十分钟把目录过一遍,后面改代码能少走很多弯路。这套工程的典型结构如下(不同版本文件名可能略有差异,但分层逻辑一致):
running-miniprogram/ ├── app.js # 全局逻辑:登录态、全局数据 ├── app.json # 页面注册、tabBar、权限声明 ├── app.wxss # 全局样式 ├── pages/ │ ├── index/ # 首页:开始跑步入口、今日数据 │ ├── running/ # 跑步中:地图、计时、里程 │ ├── record/ # 历史记录列表 │ └── detail/ # 单次跑步详情 ├── components/ │ ├── stat-card/ # 数据卡片组件 │ └── run-map/ # 地图轨迹组件 ├── utils/ │ ├── geo.js # 坐标计算、距离累加 │ ├── format.js # 时间、配速格式化 │ └── request.js # 网络请求封装 ├── cloudfunctions/ # 云函数目录(若用云开发) │ ├── login/ │ └── saveRun/ └── project.config.json # 开发者工具项目配置app.json是整个小程序的注册表,页面路径、tabBar、位置权限都在这里声明。跑步类小程序必须提前声明位置相关权限,否则真机上wx.getLocation会直接失败。pages/running是核心页面,地图、计时器、轨迹点采集都集中在这里。utils/geo.js单独抽出来是有道理的——距离计算逻辑如果散落在页面里,改一次公式要翻好几个文件,血泪经验。
2.2 技术选型:原生小程序 + 云开发
这套源码用的是微信原生小程序框架,没有上 uni-app 或 Taro 这类跨端方案。原因很实际:跑步功能强依赖微信的位置、地图、运动数据能力,跨端框架在这些原生能力上要么封装不全,要么多一层转换容易出玄学问题。原生写法虽然啰嗦,但调试路径最短。
后端部分常见做法是微信云开发:云函数处理登录和跑步数据落库,云数据库存用户和历史记录,省掉自己搭服务器的成本。如果源码里cloudfunctions目录为空或不存在,说明它走的是自建后端,那就需要看utils/request.js里的 baseURL 指向哪里。两种模式在页面层的调用方式不同,先确认清楚再动手,不然跑起来一片 404。
app.js里通常会在onLaunch中做静默登录,拿 code 换 openid,把用户标识存进全局数据。这一步是后续所有数据读写的前提,登录没通,保存跑步记录必然失败。
// app.js 登录逻辑示意 App({ globalData: { openid: '', userInfo: null }, onLaunch() { wx.login({ success: (res) => { // 用 code 换取 openid,云开发可直接调云函数 wx.cloud.callFunction({ name: 'login', data: { code: res.code }, success: (r) => { this.globalData.openid = r.result.openid; } }); } }); } });wx.login拿到的 code 只能用一次,五分钟内有效,所以必须在onLaunch里尽早换掉。globalData.openid是后续云函数识别用户身份的依据,页面里通过getApp().globalData.openid取用。如果这里没拿到值,先查云函数是否部署、wx.cloud.init是否在app.js顶部调用过。
3. 跑步核心链路拆解:从点击开始到轨迹落库
3.1 位置采集与轨迹点管理
跑步页的核心是持续采集位置点。微信提供wx.startLocationUpdate开启后台定位,配合wx.onLocationChange监听位置变化。这里有个关键点:普通wx.getLocation只取一次,跑步场景必须用持续定位接口,否则轨迹是断的。
// pages/running/running.js 位置采集片段 startTracking() { wx.startLocationUpdate({ success: () => { wx.onLocationChange((res) => { const point = { latitude: res.latitude, longitude: res.longitude, timestamp: Date.now() }; this.data.trackPoints.push(point); // 每采到一个点就累加距离 if (this.data.trackPoints.length > 1) { const last = this.data.trackPoints[this.data.trackPoints.length - 2]; const dist = getDistance(last, point); // 单位:米 this.setData({ totalDistance: this.data.totalDistance + dist }); } }); }, fail: (err) => { wx.showToast({ title: '定位开启失败', icon: 'none' }); console.error('startLocationUpdate fail', err); } }); }startLocationUpdate需要在app.json的requiredPrivateInfos里声明getLocation和onLocationChange,否则真机直接拒绝。onLocationChange的回调频率由系统控制,一般 1~3 秒一次,不要在里面做重计算,否则页面会卡。轨迹点数组trackPoints会随跑步时长增长,跑一小时可能几百个点,setData时只传必要字段,别把整个数组反复塞进视图层,这是性能翻车的常见原因。
3.2 距离计算与配速格式化
两点间距离用 Haversine 公式,utils/geo.js里通常封装成getDistance(p1, p2)。地球半径取 6371000 米,经纬度转弧度后套公式,返回米数。配速则是「总秒数 / 总公里数」,格式化后显示成5'30"这种形式。
// utils/geo.js 距离计算 const R = 6371000; function toRad(deg) { return deg * Math.PI / 180; } function getDistance(p1, p2) { const dLat = toRad(p2.latitude - p1.latitude); const dLon = toRad(p2.longitude - p1.longitude); const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(p1.latitude)) * Math.cos(toRad(p2.latitude)) * Math.sin(dLon / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(a)); }参数说明:p1、p2是含latitude、longitude的对象,返回单位为米。实际使用时要过滤掉漂移点——如果两次定位间隔很短但距离突变超过合理阈值(比如 1 秒内跳了 50 米),大概率是 GPS 漂移,直接丢弃该点,否则里程会虚高。这个阈值我一般设 30~50 米,按场景调。
3.3 结束保存与数据落库
点击结束时,把轨迹点、总距离、总时长、配速打包,调云函数或接口落库。云开发模式下写云函数saveRun,在云函数里用db.collection('runs').add写入,openid 从cloud.getWXContext()取,不信任前端传的用户标识。
// cloudfunctions/saveRun/index.js const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); exports.main = async (event) => { const { OPENID } = cloud.getWXContext(); const { trackPoints, distance, duration } = event; return await db.collection('runs').add({ data: { openid: OPENID, trackPoints, distance, duration, createdAt: db.serverDate() } }); };OPENID由微信上下文注入,前端伪造不了,这是云开发在安全上的优势。db.serverDate()用服务端时间,避免用户改手机时间导致记录顺序错乱。轨迹点数组如果很大,注意云数据库单条记录有大小限制,超长跑步建议只存抽稀后的点或分段存。
4. 跑起来之前:环境配置与真机调试要点
4.1 开发者工具导入与 AppID 配置
下载源码后,用微信开发者工具「导入项目」,选择源码根目录。project.config.json里的appid如果是占位符,要换成自己的小程序 AppID;没有 AppID 可以先用测试号,但测试号不支持云开发和部分权限,跑步定位功能会受限。导入后先看控制台有没有报错,常见的是路径大小写不一致或缺少依赖。
如果项目用了云开发,导入后要在工具里点「云开发」开通环境,把环境 ID 填到app.js的wx.cloud.init({ env: '你的环境ID' })里。云函数目录右键「上传并部署」,每个函数都要单独部署,漏一个就会在调用时报FunctionName not found。
4.2 权限声明与真机验证
模拟器的定位是模拟数据,跑步轨迹必须真机验证。真机调试前确认三件事:app.json里声明了位置权限;小程序后台的「开发管理」里配置了用户隐私保护指引,涉及位置信息要勾选;手机系统给微信开了定位权限。三处缺一处,startLocationUpdate都会失败,而且报错信息不一定直观,这是最容易卡住新手的坑。
真机预览时建议在户外走一段,观察地图上轨迹是否连续、里程是否合理。室内 GPS 信号差,轨迹会乱飘,别在室内判断代码有没有问题。
提示:云开发环境有免费额度,但跑步记录频繁写入会消耗读写次数,调试阶段可以先在本地用
console.log验证逻辑,确认无误再开云函数。
5. 避坑与常见问题排查
5.1 定位开启失败,真机无反应
现象:点开始跑步后没有任何轨迹,onLocationChange不回调。原因通常是权限链路没打通——app.json缺requiredPrivateInfos声明,或用户隐私指引没配置,或手机系统层禁了微信定位。解决:按「app.json 声明 → 小程序后台隐私指引 → 手机系统权限」顺序逐项检查,用wx.getSetting打印当前授权状态辅助定位。
5.2 里程虚高或轨迹跳点
现象:跑一公里显示三公里,地图上轨迹来回折返。原因是 GPS 漂移点被计入距离。解决:在累加距离前加阈值过滤,相邻点距离超过合理值(如 50 米)且时间间隔过短时丢弃;也可以对轨迹点做滑动平均平滑。别指望一次调好,户外实测两三次才能定下合适阈值。
5.3 云函数调用报环境不存在
现象:登录或保存时报env not found或cloud init error。原因是wx.cloud.init的 env 参数没填、填错,或云开发环境根本没开通。解决:到云开发控制台复制环境 ID,确认app.js里初始化在App()之前执行,且云函数已上传部署。
5.4 setData 频繁导致页面卡顿
现象:跑步时间越长页面越卡,计时器跳秒不跟手。原因是每次定位回调都把整个轨迹数组setData到视图层。解决:视图层只绑定当前距离、时长等标量,轨迹数组留在逻辑层,地图轨迹用polyline增量更新或降低更新频率。
5.5 历史记录列表空白
现象:跑步保存成功但记录页没数据。原因是查询条件用了前端传入的 openid 而实际存的是云函数注入的 OPENID,两者不一致;或数据库权限设置成了「仅创建者可读」但查询没带用户上下文。解决:统一在云函数里用getWXContext取 openid 查询,数据库权限按需调整。
6. 二次开发:把它改成你自己的运动产品
拿到这套源码,多数人不会原样上线,而是改成带社交或训练计划的版本。改造时我建议从数据模型入手:先在云数据库里把runs集合的字段定清楚(距离、时长、配速、轨迹、创建时间、用户标识),再动页面。字段定了,排行榜、周报、训练计划都是在这张表上做聚合查询,改起来有据可依。
排行榜的实现思路:云函数里按周或按月聚合runs,用db.collection('runs').aggregate()按 openid 分组求距离总和,再排序取前 N。注意聚合查询有性能上限,用户量大时要加时间范围索引,别全表扫。
// 周排行榜聚合示意 const $ = db.command.aggregate; const weekStart = new Date(); weekStart.setDate(weekStart.getDate() - weekStart.getDay()); const res = await db.collection('runs').aggregate() .match({ createdAt: _.gte(weekStart) }) .group({ _id: '$openid', total: $.sum('$distance') }) .sort({ total: -1 }) .limit(50) .end();match先按时间过滤缩小数据集,group按用户汇总距离,sort降序,limit控制返回量。索引要建在createdAt和openid上,否则数据一多就慢。
另一个高频改造点是轨迹回放:详情页用map组件的polyline把trackPoints画出来,配合includePoints自动缩放视野。轨迹点太多时先抽稀(比如每隔 5 个点取一个),否则polyline渲染会拖慢页面。
验证改造是否成功,我习惯用「一条完整链路走三遍」:新用户登录 → 跑一次 → 看记录和排行榜,三遍都通再提交审核。小程序审核对位置权限和隐私指引查得严,提交前把app.json里的权限声明和后台隐私指引对齐,能省一轮驳回。从那以后我每次改这类运动小程序,都强制先在真机户外走一遍完整链路再动排行榜逻辑,室内模拟数据骗得了自己骗不了审核。希望帮到你。
本文还有配套的精品资源,点击获取