简介:校园跑腿微信小程序源码面向高校学生和初中级开发者,用于搭建校园内的代取快递、代买餐食等跑腿配送服务。压缩包约八兆字节,采用zip格式,内含服务端、客户端和小程序三部分源码,同时带有项目配置、阅读指南和部署说明文档,目录结构清晰,便于本地运行和二次开发。搭建时需准备小程序前端、云服务器以及已备案域名,源码已提供前后端通信接口与业务逻辑框架,可帮助读者快速掌握微信小程序开发、接口调用和后端部署的基础流程。对于刚接触小程序开发的读者,可以从这份源码中学习完整的项目组织方式与实际业务逻辑;对于打算运营校园跑腿服务的团队,也能借助它快速搭建原型、熟悉部署链路。目前已有两千余人学习下载,值得作为入门或参考项目。
1. 校园跑腿微信小程序源码:先别急着解压,把这三点想明白再动手
校园跑腿微信小程序是高校场景里一个特别典型的真实需求——代拿快递、送饭到寝室、商店带买,单价不高但频率极高,学生团队要么自己建群手动记账,要么用这种小程序把下单、派单、结算串起来。这套源码我按「拿到手能不能立刻用、二开从哪里下手、上线前要补什么」三条线拆了一遍。适合学生团队拿来做课程设计,也适合想在校内试水的个人开发者。需要提醒的是:源码不等于成品,微信小程序的登录、手机号获取、云环境这几道关卡,官方规则这几年改得比较多,照着旧教程跑大概率翻车。下面把我的拆解过程完整走一遍。
2. 拆解项目结构:页面清单、路由关系与订单数据流
2.1 技术栈选择:为什么用原生框架而不是 uniapp 打包
这套源码是原生微信小程序技术栈,不是 uniapp 转出来的。不少同学会问,uniapp 打包成微信小程序能不能直接用?能跑,但会带过来一堆跨端兼容代码和样式冗余,而且云开发相关的调用在 uniapp 里要走 uniCloud 或者自己封装 wx 对象。如果目标是微信生态内单端落地,原生小程序是更稳的选择,至少三个优势明显:生命周期可控、调试信息直出、包体大部分情况下比重打包版本小。
选型时还涉及一个后端取舍:源码用的是微信云开发(CloudBase),而不是自建服务器。我的判断是这个选择对校园项目很划算——你不需要买域名、不需要备案、不需要自己维护 nginx 和数据库,云函数、云数据库、云存储三个能力正好覆盖跑腿业务的订单存取、图片凭证、消息通知。唯一要接受的是云开发按量计费,校园场景的并发量很低,每个月免费额度大致够用,超出也就几块钱量级。
后端选型的另一个隐性好处是权限体系。云开发数据库自带简单的安全规则,可以配置「仅创建者可读」「所有人可读但仅创建者可写」,跑腿订单这种数据天然适合用这套规则兜底。比起自建后端还要写用户系统、token 校验、接口鉴权,省掉的不只是时间,还有后续维护的精力。
2.2 页面与路由:从首页到下单的闭环
先看 pages 目录,正常会有五个核心页面:首页、下单页、订单列表、订单详情、我的。app.json 里注册页面路由时,tabBar 通常是首页、订单列表、我的三个底部项,publish 和 detail 用 navigateTo 压栈跳转。
{ "pages": [ "pages/index/index", "pages/publish/publish", "pages/orders/orders", "pages/detail/detail", "pages/my/my" ], "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/orders/orders", "text": "订单" }, { "pagePath": "pages/my/my", "text": "我的" } ] } }这段配置里,pages 数组的顺序决定启动首页,tabBar 里引用的页面必须在 pages 中登记过,且不能是分包页面。publish 和 detail 不放进 tabBar,是因为跑腿下单过程需要完整的表单上下文,底部栏切来切去容易打断填写。如果首页有个「一键发单」按钮,跳转方式就是 wx.navigateTo,在 index.js 里写:
// 从首页跳到下单页 goPublish() { wx.navigateTo({ url: '/pages/publish/publish?scene=express' }); }这里 scene 参数用来预选订单类型,publish 页面在 onLoad 里通过 options.scene 读出来。跑腿业务常见的 scene 值有 express(快递)、meal(送餐)、shop(代购),后续页面标题、价格起步费、备注模板都会按这个值切换。参数传递注意只能传字符串,对象要先 JSON.stringify,且 URL 长度有限制,别把整个订单对象塞进去。
订单详情页跳到发布页改写单子的情况也有,多数源码会在 detail 里的「再来一单」直接复制上一单的起点终点和备注,这就靠 redirectTo 而不是 navigateTo,避免页面栈越叠越深导致返回逻辑混乱。
2.3 订单数据流:状态机、数据库集合与谁去改状态
跑腿订单的生命周期相对固定,源码里如果逻辑完整,应该能看到这几个状态:等待接单、已被接单、配送中、已完成、已取消。对应到云数据库的 orders 集合,会有一个 status 字段用 0/1/2/3/-1 之类数字表示,懒一点用字符串也行,但数字枚举在云函数里做条件查询时写起来更短。
// 订单状态枚举,建议统一放在 config.js 全局引用 const ORDER_STATUS = { PENDING: 0, // 等待接单 ACCEPTED: 1, // 已被接单 DELIVERING: 2, // 配送中 DONE: 3, // 已完成 CANCELED: -1 // 已取消 }; module.exports = { ORDER_STATUS };状态流转的关键是「谁去改 status」。前端直接改库是常见的错误用法,正确做法是调云函数。云函数里带上订单 id 和期望状态做一次原子更新,同时把操作人 openid 校验进去,这样能挡住大部分恶意改单请求。以接单动作为例,云函数大致长这样:
const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database(); exports.main = async (event) => { const { orderId, openid } = event; // 先查订单,校验状态和操作人权限 const order = await db.collection('orders').doc(orderId).get(); if (!order.data) return { code: 404, msg: '订单不存在' }; if (order.data.status !== 0) return { code: 400, msg: '订单已被接走' }; if (order.data.publisher === openid) return { code: 403, msg: '不能接自己的单' }; // 原子更新:状态改为已接单,记录接单人 await db.collection('orders').doc(orderId).update({ data: { status: 1, acceptor: openid, acceptTime: db.serverDate() } }); return { code: 0, msg: 'ok' }; };openid 从哪来?云函数上下文里能直接拿 wxContext 的 OPENID,不需要前端传,前端传的 openid 反而是可伪造的,这个后面讲登录的时候细说。每次状态变更建议加一个 statusLog 字段,存一个数组,把每次流转的时间、操作方追加进去。上线后查纠纷,这就是唯一可靠的历史记录。
3. 本地跑通源码:导入工程、AppID 与云开发环境初始化
3.1 导入源码:目录结构、基础库版本与第一个编译报错
拿到源码压缩包,解压后不要直接双击打开,第一件事是启动微信开发者工具,选择「导入项目」。导入时填的项目目录要精确到包含 app.json 的那一层,有些压缩包外层还套了一层同名文件夹,选错了工具会提示“app.json: File not found”。AppID 如果你有自己的小程序账号,填自己的;没有就先用测试号,但注意测试号不支持云开发,所以想完整跑通必须注册一个小程序账号拿正式 AppID。
基础库版本建议不要太激进,开发者工具右上角「详情 → 本地设置」里把调试基础库调到 2.30.0 附近,这套源码如果要调用比较新的 API 再往上调。基础库太高,部分老旧组件样式会变;太低,云开发一些新接口不识别。第一次编译如果报「环境不存在」之类红色报错,正常,因为你还没创建云环境,先忽略它,把工程结构过一遍。
工程里值得关注的是三个文件:project.config.json 是项目配置,app.js 是全局入口,其中云开发初始化就写在这里。另一个常见坑是把 appid 写死在某个云函数里,导致换账号以后请求全部失败,这个后面避坑章节专门讲。
3.2 云开发环境初始化:集合、索引与权限规则
云开发环境需要在开发者工具左上角「云开发」按钮进去创建,创建后拿到环境 ID,比如 campus-runner- 开头的字符串。然后在 app.js 里把初始化入参改掉:
App({ onLaunch() { if (!wx.cloud) { console.error('当前基础库版本过低,请使用 2.2.3 以上'); return; } wx.cloud.init({ env: 'campus-runner-xxxx', // 替换为你的环境 ID traceUser: true // 在云开发控制台能看到用户访问记录 }); } });traceUser: true 会往用户管理里记一条访问记录,排查“到底有没有调起云能力”时很有用。云环境创建完,需要手动建集合。跑腿项目里至少四个集合:users、orders、sites(常用取送点)、feedback。创建集合时注意,orders 集合要给 status 和 createTime 加索引,否则以后订单量上来,按状态查列表会越来越慢。云开发控制台 → 数据库 → 索引管理里新建组合索引,字段选 status 和 createTime,一个升序一个降序,查询条件会快一个量级。
权限规则按业务来:users 集合建议「仅创建者可读写」,orders 集合建议「所有用户可读,仅创建者可写」,但这里说的“写”是指前端直连数据库的权限。真正改订单状态的操作都走云函数,云函数用的是管理端权限,不受这些规则限制,这就能做到让用户只能读、不能改。站点集合 sites 用「所有用户可读,仅管理端可写」,站点维护走云函数,前端只读展示。
3.3 跑通一次完整下单:从前端点击到云函数日志
环境初始化完成后,试跑一次完整下单流程,这是检验源码是否完整的黄金路径。打开首页,选一个取送点,填个测试订单,点击发布按钮,正常情况下会先调用登录逻辑拿到 openid,再调云函数创建订单。
// 发布页回调云函数示例 async onSubmit(orderData) { wx.showLoading({ title: '发布中' }); try { const res = await wx.cloud.callFunction({ name: 'order_create', data: { pickSite: orderData.pickSite, deliverSite: orderData.deliverSite, itemType: orderData.itemType, note: orderData.note || '', tip: orderData.tip || 0 } }); if (res.result.code === 0) { wx.showToast({ title: '发布成功', icon: 'success' }); setTimeout(() => wx.navigateTo({ url: '/pages/orders/orders' }), 1500); } } catch (e) { wx.showToast({ title: '发布失败', icon: 'none' }); console.error('订单发布失败', e); } finally { wx.hideLoading(); } }这段代码注意两点:callFunction 传的 data 只传业务字段,不带 openid;云函数内部通过上下文拿身份。如果你看日志发现 order_create 一直报错,去云开发控制台 → 云函数 → 选中函数 → 日志,看实时日志里的具体堆栈。常见是集合没建、环境 ID 不匹配、集合权限不允许写入三种原因,日志里都会有明确提示。
跑通一次下单后,再走一遍接单流程。用另一个微信号登录(开发者工具右上角可切换普通模式,用手机扫码真机验证也可以),在订单列表找到刚才的测试单,点击接单,然后进入详情页状态刷新。至此,核心链路就算通了。剩下的是美化样式和处理边界情况,比如用户取消订单、超时未接单自动取消,这些属于二开和优化范畴。
4. 微信登录与手机号获取:从旧 API 到新规的改造实录
4.1 wx.login 换取 openid 的完整链路
校园跑腿业务里,登录是最容易出问题的模块之一。微信小程序不像网页有 session 机制,它靠 wx.login 拿临时 code,再用 code 换 openid。云开发环境下,这个交换过程可以完全在云函数里完成,不需要自建登录服务器。常见做法是在 app.js 的 onLaunch 里先调 wx.login,拿到 code 后传给一个云函数,云函数用 cloud.getWXContext() 直接把 openid 取出来,然后把这个 openid 作为用户唯一标识存储。
// 全局登录封装 app.js 中挂到 globalData async login() { const { code } = await wx.login(); const res = await wx.cloud.callFunction({ name: 'user_login', data: { code } }); const { openid } = res.result; this.globalData.openid = openid; // 顺便检查或创建用户记录 await this.globalData.initUser(openid); return openid; }参数说明:code 有效期只有五分钟,且只能用一次,不能缓存。user_login 云函数里不需要真的拿 code 去向微信接口换 session_key,因为云开发已经把这件事封装好了,只要调用 cloud.getWXContext() 就能拿到 OPENID 和 UNIONID。自建后端的场景才需要把 code 传到后端,再调用 jscode2session 接口。这套源码用的云开发路径,前端代码里能看到 wx.login 和 callFunction 的组合。
openid 获取后存哪里?存在 globalData 够用,但如果页面刷新后要再次读取,建议把 openid 同步存到本地缓存或云数据库 users 集里。注意 openid 属于敏感数据,不要写进任何分享参数或页面 URL,微信审核对 openid 外泄是零容忍的。
4.2 手机号快捷登录:从 button open-type 到新机制的切换
跑腿业务要联系用户,手机号几乎是必填项。微信官方早期提供的获取手机号方案是 button 组件加 open-type="getPhoneNumber",但这项能力对企业主体开放,个人小程序拿不到,而且官方在陆续收紧,换到了新的手机号快速验证组件。源码里如果还在用老方法,很可能在个人主体下直接报错。这个坑必须提前说明。
<!-- 新版手机号验证按钮组件,替换旧的 open-type 写法 --> <button class="phone-login-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" >微信一键登录</button>async onGetPhoneNumber(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { wx.showToast({ title: '已取消授权', icon: 'none' }); return; } // 用 code 换手机号,code 一次性有效且 5 分钟过期 const res = await wx.cloud.callFunction({ name: 'user_getPhone', data: { code: e.detail.code } }); if (res.result.code === 0) { // 手机号明文只在云函数侧存储,前端拿到后立即脱敏 const phoneInfo = res.result.phoneInfo; this.setData({ phoneTail: phoneInfo.phoneNumber.slice(-4) }); } }这段逻辑的关键在于,前端拿到的 e.detail.code 是临时凭证,云函数用这个 code 换取手机号明文,前端不应直接看到完整号码。真实场景里手机号涉及用户隐私,展示时最多显示后四位。这也顺应了微信隐私协议的要求——在你提交小程序审核前,需要在 mp 后台的「设置 → 服务内容声明 → 用户隐私保护指引」里声明收集手机号用途,否则真机上该接口会被拦截。云函数里换手机号的具体实现依赖服务端 API,用微信官方提供的方式,这里不展开,源码自带的函数里通常有完整写法。
4.3 登录态保持:token 过期、静默续期与多角色判断
登录拿到 openid 只是第一步,更实际的问题是:用户隔几天再打开小程序,登录态还在不在?微信自带 session_key 有效期约三天,但云开发的用户态有另一套机制。常见做法是把 openid 存到本地缓存,每次冷启动读取缓存,如果存在就直接用,不存在再走 wx.login。但缓存存在不能保证用户合法,跑腿场景还涉及角色判断——发布者、接单者、管理员三个身份,它们的页面入口和按钮权限完全不同。
// 启动时检查本地登录态 checkLogin() { const openid = wx.getStorageSync('openid'); if (openid) { this.globalData.openid = openid; return true; } // 没有缓存则静默登录 return this.login(); }在订单详情页,经常要判断当前用户是否是接单人,通常在发布订单云函数里,前端用 openid 和订单数据的 publisher、acceptor 字段比对。这里注意一个顺序问题:登录态检查必须在页面 onLoad 里同步或提前完成,不能在按钮点击事件里才去登录,否则用户快速点按钮时容易触发并发登录,导致 code 被重复消费、第二次调用直接失败。
5. 避坑手册:从编译、真机到审核的五个翻车现场
5.1 翻车:开发者工具里正常,真机上请求全部失败
现象:开发者工具里页面加载正常,订单列表能刷出来,但换到真机预览,数据全空,控制台报fail url not in domain list。原因:开发者工具默认开启了「不校验合法域名」选项,本地调试绕过了域名校验,真机没这个豁免权。解决:登录 mp 后台 → 开发管理 → 开发设置 → 服务器域名,把云开发环境自带的 request 合法域名加进去,或直接使用云开发调用方式,它不依赖传统域名配置。经验是每次换环境 ID,都要重新确认域名列表。
5.2 翻车:首页顶部导航栏在部分手机上被状态栏遮挡
现象:同一套样式,iPhone 上标题位置正常,安卓真机上「我的」页面顶部文字和胶囊按钮重叠,甚至被状态栏盖住。原因:不同机型的导航栏高度不统一,源码里如果写死了导航栏高度,必然出问题。解决:用 wx.getMenuButtonBoundingClientRect() 拿胶囊按钮位置,动态计算导航栏高度,或者使用自定义 navigationStyle,在 components 里封装一个通用的顶部导航组件。
// 计算导航栏高度的通用逻辑 const menu = wx.getMenuButtonBoundingClientRect(); const statusBarH = wx.getSystemInfoSync().statusBarHeight; const navBarH = (menu.top - statusBarH) * 2 + menu.height;这套逻辑几乎是所有微信小程序的标配,校园跑腿源码里如果没封装,二开时要自己补上。否则光一个顶部适配就能让测试同学在群里发几十条吐槽。
5.3 翻车:接单后订单列表不刷新,必须重新进小程序
现象:用户 A 发布订单,用户 B 接单后,A 的订单列表还显示「等待接单」,下拉刷新也不生效。原因:订单列表页 onShow 里没有重新拉数据,或者数据存在全局缓存里没失效。解决:在订单列表页的 onShow 生命周期里强制重新拉取列表,不能只在 onLoad 里查一次。
onShow() { this.fetchOrders(); // 每次页面显示都拉最新数据 }, onPullDownRefresh() { this.fetchOrders().finally(() => wx.stopPullDownRefresh()); }接单操作本身是云函数改状态,前端列表刷新只是重新查询的问题。但如果列表数据量变大,每次都全量重查会明显变慢,后续可以改成按订单状态增量拉取,或在返回值里带一个 updateTime 字段做节流。新手先别优化,保证每次 onShow 都刷新,这是正确性优先的做法。
5.4 翻车:主包体积超过 2MB 限制,上传失败
现象:开发者工具上传时提示main package size exceed 2MB。原因:图片资源、图标库、第三方 UI 组件都堆在主包里。跑腿小程序如果加了自定义地图组件和大量 icon,超过 2MB 很容易。解决:优先做分包,将不需要首屏加载的页面拆到 subpackages,比如订单详情、发布页、我的页面。静态图片放到云存储或 CDN,不在工程里保留大体积资源。
{ "subpackages": [ { "root": "pages/detail", "pages": ["detail"] }, { "root": "pages/publish", "pages": ["publish"] } ] }分包时注意 tabBar 页面不能放分包里,相关引用路径要全部改成相对路径。大部分跑腿源码主包超限的罪魁祸首其实是 mp 里引用的字体文件和弹层动画组件,排查时先看 package.json 依赖和 assets 目录,再考虑拆页面。这里分享一个血泪经验——不要图省事把整张地图 SDK 和所有绘图资源都打进去,用腾讯位置服务的小程序地图组件按需加载,包体能直接瘦掉三分之一。
5.5 翻车:手机号获取在个人主体小程序上报错
现象:按教程写了 getPhoneNumber 按钮,开发者工具调试正常,真机体验版弹出的授权框却是灰色不可点,后台报错提示无权限。原因:手机号相关能力要求小程序账号为企业主体,且需要完成微信认证,个人主体没有接口权限。解决:确认账号主体,如果是个人开发者,只能引导用户手动填写手机号,做格式校验后存入数据库。如果想保留一键获取体验,需要申请企业小程序,微信认证费用一年三百元。校园项目建议先评估预算再决定,个人开发的话就走手动填写方案,不影响核心流程跑通,只是多一步输入操作。
5.6 翻车:iOS 上日期时间选择器显示异常,默认值不生效
现象:下单页的点选配送时间,安卓显示正常,iOS 上日期选择器一片空白,或确认后显示Invalid Date。原因:picker 的 value 格式与 iOS 的 JS Date 解析规则不完全兼容,2024-5-6 14:30这种格式在 iOS 上解析失败,安卓的解析器更宽容。解决:统一把时间格式化成YYYY-MM-DDTHH:mm,也就是 ISO 8601 格式,传进 picker 前先做一次手工格式化,不要直接塞 Date.now() 的毫秒数或简写字符串。
// 格式化时间的兼容写法 function formatTimeForPicker(time) { const d = new Date(time); const pad = n => (n < 10 ? '0' + n : n); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }这类问题在开发者工具里永远复现不出来,因为开发者工具底层跑在 PC 的浏览器引擎上,只有真机上的 JSCore 才会暴露差异。所以凡是涉及时间格式化、正则解析、字符串拆解的代码,我一般都会在真机自测列表里专门标一项。
6. 上线前最后一公里:域名、隐私弹窗与体验版实测清单
6.1 体验版自测:别只在开发者工具里点按钮
提交审核之前,先把代码上传,在 mp 后台设置成体验版,用手机扫码走一遍真实链路。体验版和正式版的区别只有用户访问入口,接口权限、云环境、包体积限制全部一致。自测至少要覆盖五个场景:首次登录、发布订单、接单、完成订单、取消订单。每个场景分开登录两个微信号交替操作,单号双号都要试。开发者工具的「真机调试」功能可以边真机操作边看 console 实时日志,比体验版盲测好用得多,我每次都会开这个窗口再开始动手。
6.2 隐私弹窗与服务声明
2023 年以后,小程序调用手机号、位置、摄像头等隐私接口,微信会强制要求配置「用户隐私保护指引」。不配置的后果是:审核被拒,或真机上调用相关接口时直接无响应。操作入口在 mp 后台「设置 → 服务内容声明 → 用户隐私保护指引」,你需要逐条勾选收集的信息类型,并填写用途说明。跑腿小程序通常涉及位置信息、手机号、头像昵称,每一项都要写清楚用于订单配送和联系取送双方。另有一步是代码里调用 wx.requirePrivacySetup() 或等系统自动弹出隐私弹窗,如果源码里没有接口引导,首次启动时微信会基于后台配置自动处理,不必额外担心。
6.3 审核被拒的常见理由与改法
我见过不少跑腿小程序在审核环节翻车,翻车理由高度重复。第一,类目不符——小程序服务类目选的是「生活服务」,但跑腿配送需要补充「快递/配送」相关类目,各地审核尺度稍有差异,但原则上选择贴近实际业务的类目。第二,无客服能力——审核要求小程序页面必须有明确的客服入口,按钮绑定微信客服组件即可。第三,测试账号无法完成完整流程——审核人员用他们自己的微信号体验,如果你设置了仅校园网 IP 可访问,或订单必须由管理员指派,他们会直接判定功能不可用。正确做法是给审核人员留一个明确路径:打开首页就能看到一个演示订单,一步即可完成接单。
最后一件事是我的个人习惯:每次提交审核之前,我都会换一台备用机、换一个从来没在这台设备上登录过微信的账号,清空缓存重新走一遍完整流程。因为开发者自己调试千百次都是带缓存的,而审核人员永远是冷启动加陌生用户身份,这两个变量恰恰最容易暴露缓存残留、登录态丢失和权限遗漏的问题。从那以后,我把「新增功能 → 换号冷启动实测」写成每次发布的强制步骤,跑腿小程序这种多角色交互的项目,这一遍检查往往比功能自测更救命。希望帮到你。
本文还有配套的精品资源,点击获取