资讯动态

校园跑腿小程序实战:Flask+uni-app全栈开发与避坑指南

发布时间:2026/10/3 9:23:20 来源:尧图企业网站定制
最近被问得最多的一个项目就是校园跑腿加食堂超市的小程序。我帮学校信息中心搭了一套后端用 Python 的 Flask前端用 uni-app 编译成微信小程序跑了一个学期高峰期每天几百单整体还算稳。这篇文章把整个项目的选型思路、数据库设计、接口权限、前端适配、打包上架和排障过程完整整理一遍给正准备做校园 O2O 项目、或者毕业设计选类似题目的朋友一个可以直接抄作业的参考。这类项目看起来只是“下单 配送”但真做起来要同时搞定用户端、跑腿员端、商家端和管理后台还牵扯微信支付、订单状态流转、多端打包这些问题。我会尽量把关键环节和踩过的坑写细少让你走弯路。1. 项目整体设计与技术选型1.1 需求拆解从“跑腿”到“食堂超市”的业务链路刚接到这个需求的时候第一反应不是急着写代码而是先把业务流程理顺。校园跑腿听起来很简单但如果同时包含食堂点餐、超市代购、快递代取三种业务后台逻辑会差挺多。食堂点餐是商家备餐后骑手去取超市代购是骑手拿着购物清单去逛超市快递代取是纯跑腿前端页面和订单字段都不一样。最终我把业务拆成四个角色学生用户、跑腿员、商家或档口、管理员。学生用户能浏览食堂档口和超市商品下单后选择自取还是配送配送单会被跑腿员抢单骑手完成取货送达后订单进入完成状态商家可以在商家端 App 上架商品、接单出餐管理员在后台做用户管理、订单干预、佣金比例和退款处理。这里面最容易忽略的是“配送距离和费用”的计算。如果做成固定配送费跑腿员不愿意接远单如果全部按实际距离算又要引入地图 API。我们第一期选择了“按楼栋区域区分”把学校宿舍区、教学区拆成若干区域每个区域固定配送费比如同栋 3 元、跨区 5 元后续再做距离计价。这个决定省了大量开发时间也符合学生习惯。1.2 技术选型为什么是 Flask 和 uni-app后端语言我选了 Python Flask没有选 Django。原因很简单这个项目接口量不大大概三十多个接口Flask 的蓝图和 RESTful 风格足够组织清晰而且 Flask 部署轻量在一台 2C4G 的学生服务器上用 gunicorn 就能跑起来。Django 自带 admin 和 ORM 确实香但杀鸡用牛刀很多配置反而成了学习成本。前端用 uni-app 不是因为它比原生小程序好而是因为多端能力强。当时学校还要求做一个供商家和跑腿员用的 App如果用原生微信小程序写一版再要求安卓 App工作量直接乘 2。uni-app 是 Vue 语法写一套代码能编译到小程序、H5 和 Android App。虽然有些坑后面排障部分会讲但对团队就两三个人、还要做管理端的项目来说性价比很高。选了 uni-app 还有一个隐性收益组件生态。uview-plus 这种 UI 库对表单、表格、列表分页支持不错比自己写原生组件快很多。关于组件导入踩坑我放到第二章讲。1.3 项目目录规划一眼能看懂的工程结构项目分两个仓库后端是 flask-server前端是 uniapp-client。我习惯按功能模块而不是按文件类型划分后端放 api、models、services、utils前端放页面、组件、公共样式。大概结构长这样flask-server/ app/ api/ # 蓝图auth.py / shop.py / order.py / user.py models/ # SQLAlchemy 模型 services/ # 业务逻辑订单创建、支付回调、对账 utils/ # 装饰器、状态码、微信工具 config.py run.py requirements.txt uniapp-client/ pages/ # index/ order/ user/ rider/ merchant/ components/ # 自定义组件 utils/ # request.js / auth.js static/ # tabbar 图标等 pages.json manifest.json目录结构看起来普通但对后续维护很重要。尤其后端的订单创建逻辑我坚持放 service 而不是 controller 里这样微信支付回调也要调同一套逻辑避免同样的代码写两遍。前端 utils/request.js 统一封装了 token 注入和 401 跳转后面所有页面调接口只关心业务数据。项目做到一半你会发现好的目录不是给代码看的是给人省脑力的。2. Flask 后端数据库、登录态和订单状态机2.1 数据库表设计一张表讲清人和钱的关系业务跑得顺不顺数据库设计起一半作用。我建了七张核心表基本够覆盖校园跑腿和食堂超市场景先看表格表名用途核心字段备注user用户和角色id, openid, phone, role, balance, statusrole 区分 student/rider/adminmerchant商家或档口id, user_id, name, category, status, image每个商家绑定一个用户账号product商品id, merchant_id, name, price, stock, sales, image超市商品和食堂菜品共用address收货地址id, user_id, region, detail, tag按楼栋区域配送order订单主表id, order_no, user_id, rider_id, merchant_id, status, amount, delivery_type, create_time配送费单列order_item订单商品明细id, order_id, product_id, quantity, price高并发下单价快照wallet_log钱包流水id, user_id, amount, type, balance_after, remark下单、退款、佣金都要记核心是 order 和 order_item 分离。比如用户点了三个菜加一桶泡面order 表记录订单总额和配送费order_item 记录每个商品当时的单价和数量。为什么必须有 order_item因为商品价格会变如果订单表里只存一个 total_amount商家改价后订单历史就说不清了。我在写第二版时还加了 product_snapshot 字段把商品名称、图片、规格、单价整段 JSON 存进去退款对账时直接回看。钱包流水表很多初学者会忽略但跑腿项目一定要有。骑手每完成一单佣金进入钱包学生退款时金额从钱包扣回每一笔金额变动都必须有流水。我规定所有金额操作一律通过 wallet_log 记录禁止直接改 user.balance 字段这样即使对账不平也能靠流水反查。2.2 微信登录与 JWT 权限别把 openid 当登录态微信小程序登录的原理不复杂前端调用 wx.login 拿到一个临时 code后端拿这个 code 去微信接口换 openid 和 session_key。openid 是用户在小程序里的唯一身份但你不能把 openid 直接返回给前端当 token因为它只是标识不带过期和校验信息而且 session_key 是敏感信息绝不能被前端拿到。我这边是后端用 JWT 签发一个带 user_id 和过期时间的 token。后端登录接口的关键代码如下auth_bp.route(/api/auth/wx_login, methods[POST]) def wx_login(): data request.get_json() code data.get(code, ) params { appid: app.config[WX_APPID], secret: app.config[WX_SECRET], js_code: code, grant_type: authorization_code } wx_resp requests.get( https://api.weixin.qq.com/sns/jscode2session, paramsparams, timeout5 ).json() if openid not in wx_resp: return jsonify({code: -1, message: 微信登录失败}) openid wx_resp[openid] user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid, nickname新用户, rolestudent, balance0) db.session.add(user) db.session.commit() payload {user_id: user.id, exp: int(time.time()) 7200} token jwt.encode(payload, app.config[SECRET_KEY], algorithmHS256) return jsonify({code: 0, data: {token: token, user_info: user.to_dict()}})注意两个坑第一code 只能用一次前端不要重复发登录请求否则第二次会报 invalid code。第二requests 请求微信接口要设置超时不要用默认阻塞我习惯加 timeout5微信接口偶尔抽风不能拖垮整个接口。JWT 校验装饰器我用在几乎所有业务接口上。为了让代码可读我用 flask 的 g 对象保存当前用户不在 request 上塞自定义属性from functools import wraps from flask import g, request, jsonify import jwt def login_required(fn): wraps(fn) def wrapper(*args, **kwargs): token request.headers.get(Authorization, ) if token.startswith(Bearer ): token token[7:] try: payload jwt.decode(token, app.config[SECRET_KEY], algorithms[HS256]) g.user_id payload[user_id] except jwt.ExpiredSignatureError: return jsonify({code: 401, message: 登录已过期}) except jwt.InvalidTokenError: return jsonify({code: 401, message: 无效凭证}) return fn(*args, **kwargs) return wrapper后续接口里拿 g.user_id 再去查 User这样每个接口不需要重复解析 token 逻辑。2.3 订单状态机从待支付到已完成有哪些分支订单是这类项目里最容易出 bug 的地方所以我专门整理了一套状态流转。订单状态我定义为状态值含义触发动作pending_payment已下单待支付用户点击提交订单货锁 10 分钟paid已支付待接单支付回调成功后accepted跑腿员已接单抢单或管理员指派delivering配送中跑腿员点击取货完成completed已送达或已收货学生确认或超时自动确认cancelled已取消支付前用户取消管理员强制取消refunding退款中售后申请后状态流转不是所有方向都允许。比如 pending_payment 只能到 paid 或 cancelledpaid 只能到 accepted 或 cancelledaccepted 不能直接跳到 completed必须先 delivering。我写接口时没有用 if 满地乱写而是维护了一个允许流转字典ORDER_STATUS_TRANSITION { pending_payment: [paid, cancelled], paid: [accepted, cancelled], accepted: [delivering, cancelled], delivering: [completed], completed: [], cancelled: [], refunding: [completed, cancelled] }每次更新订单状态前先判断前一步是否在允许列表里不在就直接报“状态异常”。这个设计省了后面排查并发乱改状态的力气。还有一点学生支付后订单里的“锁货”状态要释放最简单做法是创建订单时给订单写一个 pay_expire 字段定时任务扫描超时未支付订单把库存加回去并置为超时取消。2.4 支付回调的幂等处理优惠券和红包如果业务不复杂可以先不做但支付回调的幂等处理必须一开始就做好。微信支付回调可能会因为网络原因重试同一个订单如果回调处理两遍余额和库存就双倍扣减。我当时的做法回调处理函数第一步查询订单如果订单已经处于 paid直接返回 success不再重复处理。只有待支付订单才允许进入支付成功后的流水扣减、库存扣减、日志写入。这个判断看起来简单却是保证财务数据准确的底线。具体在 Flask 里处理支付回调时还需要对微信返回的 xml 和 sign 做校验。验签失败直接记录日志并返回错误避免伪造回调。出于篇幅不贴太多代码但“先查订单状态再改数据最后回执 success”这个顺序一定要保证。3. uni-app 前端页面、导航栏和列表加载更多3.1 初始化项目插件市场导入 uview-plus 比 npm 省心前端我用 HBuilderX 创建 uni-app 项目模板选 Vue3。创建完之后第一件事是引入 UI 组件库我用的 uview-plus。在 HBuilderX 里切换到插件市场直接找到 uview-plus选择“下载插件并导入项目”这个方式比 npm 安装省心因为 npm 版本经常和 HBuilderX 的编译链有兼容问题。我踩过一次坑Vue3 项目用了 uview 老版本一编译就报找不到组件。导入之后要配 pages.json 里的 easycom 规则才能直接在模板中用 u-button、u-input 这类组件而不用手动 import。配置如下easycom: { autoscan: true, custom: { ^u--(.*): uview-plus/components/u-$1/u-$1.vue, ^up-(.*): uview-plus/components/u-$1/u-$1.vue } }然后在 main.js 里注册import { createSSRApp } from vue import uviewPlus from uview-plus import App from ./App.vue export function createApp() { const app createSSRApp(App) app.use(uviewPlus) return { app } }uview-plus 的按需引入在 HBuilderX 里需要手动开启分包配置如果项目超过 2MB 也可以上微信分包但早期别折腾直接全量注册更稳。3.2 tabbar 配置和顶部导航栏高度适配小程序底部 tabbar 必须在 pages.json 里声明不能用普通页面里写死一个 div 模拟否则底部栏不会跟随页面切换。tabbar 的 iconPath 和 selectedIconPath 只支持本地路径不能放网络 URL很多新手在这里被坑。我们 tabbar 三个页面首页、订单、我的配置参考如下tabBar: { color: #808080, selectedColor: #1E82D4, backgroundColor: #ffffff, list: [ { pagePath: pages/index/index, text: 首页, iconPath: static/tab/home.png, selectedIconPath: static/tab/home-active.png }, { pagePath: pages/order/order, text: 订单, iconPath: static/tab/order.png, selectedIconPath: static/tab/order-active.png }, { pagePath: pages/user/user, text: 我的, iconPath: static/tab/user.png, selectedIconPath: static/tab/user-active.png } ] }顶部导航栏高度是另一个麻烦。微信小程序的导航栏区域由状态栏和胶囊按钮组成不同机型胶囊位置不一样如果用自定义导航栏不能直接写死 44px。我封装了一个工具方法在 App.vue 里计算export function getNavBarInfo() { const systemInfo uni.getSystemInfoSync() const statusBarHeight systemInfo.statusBarHeight const menuBtn uni.getMenuButtonBoundingClientRect ? uni.getMenuButtonBoundingClientRect() : null let navBarHeight 44 if (menuBtn) { navBarHeight (menuBtn.top - statusBarHeight) * 2 menuBtn.height } return { statusBarHeight, navBarHeight } }拿到数据后放到 globalData自定义导航栏组件里用 padding-top 把状态栏高度撑开标题高度用 navBarHeight 控制。实测不同机型差距很大有的胶囊顶部离状态栏底部只有 12px有的超过 18px写死会歪。3.3 列表加载更多小程序分页的正确打开方式食堂点餐页和订单列表都涉及“下拉加载更多”。最好的实现是页面滚动到底部时触发的 onReachBottom 生命周期。这里有个要点分页状态下要区分“第一页”和“加载中”不能在请求还没回来时再次触发否则大量重复请求会把后端打挂。我的 loadList 模板data() { return { pageNum: 1, pageSize: 10, total: 0, list: [], loading: false, finished: false } }, async loadList() { if (this.loading || this.finished) return this.loading true try { const res await request({ url: /api/order/list, data: { pageNum: this.pageNum, pageSize: this.pageSize } }) this.total res.data.total this.list this.pageNum 1 ? res.data.list : this.list.concat(res.data.list) this.finished this.list.length this.total } finally { this.loading false } }, onReachBottom() { if (this.finished) return this.pageNum 1 this.loadList() }, onPullDownRefresh() { this.pageNum 1 this.finished false this.loadList().finally(() uni.stopPullDownRefresh()) }注意onReachBottom 并不是所有页面默认开启需要在 pages.json 对应页面配置里开启“下拉刷新”才能配合 onPullDownRefresh。实际中列表页还要处理好空态“暂无订单”的占位不要用文字居中最好配一张小图和重新加载按钮用户体验差别很大。另外小程序列表页的数据量不要贪多pageSize 我通常设 10超过 20 在某些低端安卓机上滚动会卡。3.4 订单状态轮询、自定义分享和小程序生命周期订单状态是动态的理论上用 WebSocket 最实时但小程序 WebSocket 在切后台后很容易断开重连逻辑要写很多。我们项目最终妥协成轮询订单详情页进入后立即拉一次然后每 15 秒用 setInterval 拉状态页面 onUnload 或 onHide 时 clearInterval。实测对几百单规模完全够用用户感知不到 15 秒延迟。分享功能是校园项目的重要传播渠道。小程序需要用 onShareAppMessage 回调配置分享内容微信小程序还支持 onShareTimeline 分享到朋友圈。自定义分享消息onShareAppMessage() { return { title: 帮同学带饭跑腿接单快人一步, path: /pages/index/index?invite${this.myUserId}, imageUrl: https://your-domain.com/share-banner.png } }监听用户离开小程序则在 App.vue 里写 onHide我们用它做一件事如果用户停留在下单编辑页超过时间离开小程序时自动保存草稿避免订单内容丢失。但要注意 onHide 在切后台和跳转其他小程序都会触发不能在这里做需要网络返回的强操作只适合发一个轻量级保存请求。3.5 单选框、表单校验和页面权限跳转食堂点餐里的“自取/配送”选择小程序原生可以用 radio-group但 uview-plus 的 u-radio 在视觉和事件处理上更一致。要注意三点radio 的 value 是字符串和数字比较要 toString表单提交前要校验地址和配送区域不然骑手会送到错误位置商家端页面要在 onShow 里校验用户角色不是管理员就 redirect 到登录页不能只隐藏入口不拦截接口。我用 uview-plus 的 u--form 组件做表单校验rules 里的 type 不要和数据结构冲突。比如配送费选中的是数字但组件返回字符串提交前必须 Number() 转换否则后端模板匹配的 int 类型会报错。这些细节虽然小但在真机测试时最容易发现。4. 从 HBuilderX 到微信小程序打包、审核与上架4.1 manifest.json 配置和小程序运行调试一个 uni-app 项目对应一套 manifest.json这里集中配置不同端的 AppID、权限、SDK 等。微信小程序端关键配置是“微信小程序 AppID”没有它只能预览不能真机上传。第一次运行前先确保 HBuilderX 顶部“运行”菜单可以下载微信开发者工具调试服务然后在微信开发者工具里把“服务端口”打开HBuilderX 才能把编译产物传过去。manifest 里还有权限声明比如获取位置、相册、摄像头。但注意微信小程序端权限其实在微信公众平台后台的“开发管理 - 接口设置”里申请不是 manifest 里勾了就行。我做项目初期花了半天以为 manifest 配置好定位就能用结果微信开发者工具里一直报 auth denied后来去公众平台把 wx.getLocation 权限申请下来才解决。4.2 体验版分发怎么把小程序发给朋友试用把小程序发给别人试用不是直接把项目打包发微信。常规流程是在微信开发者工具点击“上传”填版本号和备注然后到微信公众平台后台“版本管理”里把它设为体验版生成体验版二维码。关键是必须先在“成员管理”里把测试微信账号添加为体验成员否则扫码也打不开。一般上传后等 1-2 分钟设置体验版后立刻能扫。这里有个技巧体验版请求后端接口用的是真实服务器地址如果后端还没配置 https 证书微信开发者工具会报“无效的域名”。可以临时在开发者工具里勾选“不校验合法域名”但体验版手机扫码就没办法绕过必须把合法域名加到小程序后台而且要确保是 https。我建议后端一开始就配好域名和 SSL 证书不要等上线前再补。4.3 安卓应用市场的上架经验如果要打包成安卓 App 再上各应用市场第一步是在 HBuilderX 里使用“发行 - 原生 App - 云打包”需要配置 Android 打包证书。证书用一个 .jks 文件设置好 keystore 密码和 alias这个文件必须留存好后面更新版本要用同一个证书签名丢了就永远没法覆盖安装。我这次也用同一套 uni-app 代码打过安卓包踩了两个坑一是云打包的包名和 manifest 里 AppID 不一致导致推送不能用二是上架时各市场要求提供软件著作权、隐私政策说明跑腿类 App 通常还需要企业主体资质。如果只是个人开发者上架难度不小建议直接走微信小程序先用起来再考虑 App。微信小程序有“年审”概念主要是企业主体认证每年要审核一次。校园项目挂在学院名下时认证到期前一个月后台会提醒记得安排学校管理员接口处理否则小程序会被暂停线上服务。个人主体的小程序不需要走年审但企业主体需要。这块属于运营事项技术侧别等白屏了才知道。5. 常见问题与排障实录5.1 uni-app 不打印日志信息先看编译环境很多朋友用 HBuilderX 真机运行 uni-app 时发现 console.log 不输出第一反应是代码写错了。其实最常见原因是 HBuilderX 控制台的日志级别被过滤掉了或者在“运行到手机或模拟器”时没有打开日志级别的 Verbose。我习惯在代码里封装一个统一的 log 方法export function log(...args) { if (process.env.NODE_ENV development) { console.log([app], ...args) } }发布后会自己消失不用删。如果真机还是看不到试试用 uni.showToast 输出关键变量虽然丑但定位问题很快。另外微信开发者工具里的 console 和 HBuilderX 控制台是两套别混着看。5.2 真机抓包用 Charles 看微信小程序请求小程序里所有接口都是 HTTPS真机调试时用微信开发者工具能看到一部分请求但那是在开发工具环境里真机上有些问题复现不了这时候就要抓包。我用的工具是 Charles步骤不复杂手机和电脑连同一个局域网手机 WiFi 代理设到电脑 IP 的 8888 端口安装 Charles 根证书到手机上就能看到小程序发出的 HTTPS 请求内容。但要注意小程序中的合法域名校验和证书信任机制会让部分请求在代理下直接失败这是正常现象。抓包最常解决的问题是真机上接口偶发超时、并发请求被阻塞、cookie 和 token 头没带上这些在开发工具模拟器里很难复现抓包一看就明白。我每次排查接口问题基本都是这个顺序先看后端日志再看 Charles 请求头然后看返回体最后才会怀疑前端页面逻辑。5.3 输入法顶起、tabbar 错位和视频旋转等兼容问题安卓机上页面里的输入框聚焦时整个页面包括 tabbar 都会被输入法顶起原因是默认 adjust-position 把页面整体上移。如果你用的是自定义 tabbar这个问题更明显。解决办法是把 input 的 adjust-position 设为 false然后自己监听键盘高度计算输入框位置。uni-app 里 input 组件有 cursor-spacing 属性可以撑开输入框与输入法之间的距离。但有时候并不生效最彻底的做法是弹出一个独立的下单抽屉原生输入框不用键盘时收起这样底栏不参与布局。我们最后是改了布局才彻底解决。视频旋转是另一个安卓机上的怪问题部分安卓机型拍摄视频时记录的旋转角度和播放器解析方式不一致。我用 uni-app 的 chooseVideo 拿到临时文件后需要读视频的 orientation 字段如果非 up 方向就调用服务端 ffmpeg 旋转或前端 canvas 重新编码。在校园跑腿场景里视频主要用于跑腿员上传取货照片和视频留证这个小坑很烦人后来直接要求骑手只拍照不录视频业务上更合理技术上少一个坑。如果必须录视频建议用 video 组件的 show-center-play-btn 配置或者集成原生插件不要试图在小程序里转码性能撑不住。5.4 H5 唤起微信小程序链接无法访问很多项目会把校园跑腿服务同时挂到微信公众号 H5 里用户通过 H5 链接唤起小程序但这个链接有时报“无法访问”。原因是微信的开放标签 wx-open-launch-weapp 必须在 H5 页面里处于微信公众号的 JS 接口安全域名下且 H5 链接和要打开的小程序必须绑定在同一个开放平台账号下。你在普通浏览器或非微信内置浏览器打开链接本来就不会生效。排查顺序确认微信浏览器、确认公众号和小程序是否绑定同一开放平台、确认 JS-SDK 签名没失效。我们最后干脆不在 H5 里走跳转直接展示“复制口令到微信小程序搜索”复杂问题用土办法解决。5.5 Flask 部署和 Python 环境问题速查后端部署这步虽然项目标题里已经写了 Flask但很多新手会卡在 Python 环境上。最稳的组合是服务器 Linux Python3 虚拟环境 gunicorn Nginx 反代。基本命令python3 -m venv venv source venv/bin/activate pip install -r requirements.txt gunicorn -w 4 -b 127.0.0.1:8000 run:app然后用 Nginx 把 80/443 端口转到 8000并配上 SSL 证书。如果你用 pip 安装某个库报“无法安装”先说三件事确认当前在虚拟环境里确认 pip 是 python3 的 pip确认 requirements.txt 里没有把包名写错。有一回我们 orders 表有中文MySQL 连接串少了一个 charsetutf8mb4 参数导致商品名全部乱码后来在 config 里加了一行才解决。这种问题不像报错那么醒目全靠翻日志。如果你是刚装 Python记得官网下载安装包时勾选 Add to PATH否则后面在命令行敲 python 会提示不是内部命令。Flask 的 debug 模式只能用在开发环境线上用 debugTrue 会暴露调试器还可能造成代码热重载混乱。我用 gunicorn 启动时根本不会把 debug 带过去但很多同学本地 run.py 里写的 debugTrue部署时不改就直接跑起来一旦崩溃页面会跳出一堆敏感信息。5.6 常见问题速查表把上面这些坑和排查建议整理成一张速查表方便你直接对号入座问题可能原因解决方案uni-app 控制台没有 console 输出HBuilderX 日志级别过滤或发布版开发环境用 console.log封装 log 方法自定义导航栏错位没有获取状态栏高度用 getMenuButtonBoundingClientRect 计算列表加载重复请求没有加 loading 锁请求前置 if (loading/finished) returntabbar 被键盘顶起adjust-position 默认开启输入框 adjust-positionfalse或用弹出层微信登录 code 无效code 重复使用登录请求只发一次后端缓存 code支付回调重复处理未做幂等回调先查订单状态已支付直接返回 success视频旋转安卓 orientation 异常换图片或服务端 ffmpeg 转码H5 打开小程序失败未绑定同一开放平台或非微信浏览器检查开放平台绑定和 JS-SDKFlask 部署后中文乱码数据库连接没有 charsetutf8mb4配置 SQLAlchemy 连接串小程序真机请求失败域名未加入合法域名或未配置 https后台配置 request 合法域名这些坑不是一次踩完的前前后后花了大概一周时间在联调和真机测试上。如果再做一遍我会更早做真机预发环境而不是等到开发完才集中测。有一点可以肯定校园跑腿类项目的核心不是炫技而是把订单、支付、配送这几个流程搞得足够扎实前端 UI 丑一点没关系订单状态错乱一天口碑就崩了。最后再分享一个小技巧上线后每天定时把订单流水、钱包流水、退款记录三张表导出一次放在一个独立目录里方便财务对账也能在出纠纷时快速还原现场。这个需求听起来不高级但真到了同学来投诉“没收到退款”的时候你会庆幸有这堆流水。如果你正准备做类似项目别纠结于技术栈是不是最前沿先保证业务流程闭环再用好 Flask 和 uni-app 这两个趁手工具跑起来比什么都重要。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑