恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
uniapp开发奶茶店点餐微信小程序全流程实战与避坑指南
首页
资讯中心
/
uniapp开发奶茶店点餐微信小程序全流程实战与避坑指南
uniapp开发奶茶店点餐微信小程序全流程实战与避坑指南
发布时间:2026/8/31 15:19:03
简介这是一套面向中小型餐饮商户与全栈开发学习者的奶茶店数字化点餐解决方案基于uniapp实现跨端兼容同时支持微信小程序与H5页面双模式部署后端采用Spring Boot构建RESTful接口管理后台使用Vue.js开发兼顾业务落地与技术教学需求。资源包共481个文件涵盖101个Java后端逻辑文件、57个JS交互脚本、50个Vue组件、106个PNG与104个JPG静态资源以及YML配置、SQL建表语句、SCSS样式等关键工程文件整体压缩后仅5.68MB结构清晰、模块解耦度高。目前已有930人学习下载适合希望掌握小程序VueSpring Boot全栈开发流程的中级开发者可直接部署运行包含完整订单管理、商品分类、购物车、支付回调模拟及公众号H5适配配置说明代码注释充分便于二次开发与教学演示。 上个月帮一位开奶茶店的朋友折腾了一套点餐微信小程序前后改了差不多两周把uniapp从编译到上线的各种边角料问题都踩了一遍。这套源码现在还在店里用着顾客扫码点单、店员后台接单高峰期反应速度还不错。如果你正打算用uniapp做一套奶茶店或饮品店的点餐微信小程序或者手头已经拿了一份源码但不知道从哪下手、不知道改哪些地方这篇应该能帮你省掉大量翻文档和查issue的时间。我会按照“技术选型 → 工程结构 → 核心功能实现 → 微信端适配坑 → 后端数据对接 → 上线发布”这条主线来讲每一段都会给出我实际验证过的代码和配置不是纯理论分析。尤其针对微信小程序里常见的顶部导航栏高度、单选框样式、分白屏、自定义分享这几个问题我会把排查思路和最终解决办法都写出来这些都是纯文档里不会告诉你的细节。1. 为什么选uniapp做点餐小程序一次想清楚的技术选型1.1 点餐场景的核心诉求奶茶店点餐小程序看起来功能不多但实际业务链路并不短。顾客进店扫码后首先看到的是商品分类和商品列表点击某个商品弹出规格选择大小杯、糖度、冰量、加料然后加购、修改购物车、下单结算最后查看订单状态。这个过程里最容易被忽略但最重要的一点是“操作要快”。奶茶店高峰期的特点是短时间大量并发订单顾客不会像逛淘宝一样慢慢看他们希望三步之内完成点单。所以技术方案必须保证页面切换流畅、购物车状态实时同步而且不能因为某个页面加载缓慢导致顾客流失。这决定了前端技术栈的核心要求视图层更新效率高状态管理方便开发效率还要足够快。点餐业务逻辑不复杂但页面交互细节多规格弹窗、购物车浮层、角标数字都需要频繁响应用户操作。1.2 uniapp对比原生微信小程序的真实差异很多人在开始之前都会纠结一个问题既然只做微信小程序为什么不直接用原生非要套一层uniapp我一开始也纠结过这个问题但实际对比下来uniapp在这个场景里有几个不可替代的优势。第一是跨端复用的可能性。店铺经营不是只靠一个微信小程序很多奶茶店还会做抖音小程序、支付宝小程序甚至后续要做H5方便顾客在朋友圈里分享点单链接。如果全用原生开发每端都要写一套代码。uniapp用一套代码可以同时编译到微信小程序、支付宝小程序、抖音小程序和H5未来生意扩展时的边际成本非常低。第二是开发效率。原生小程序对组件化的支持虽然也在进步但很多UI交互还是得手写再加上wxml和wxss的语法相对独立会Vue的人还得重新适应一套模板语法。uniapp基于Vue的语法模型会Vue的开发者几乎零成本上手而且它有丰富的插件市场类似规格弹窗、滚动分类、侧边栏联动这种点餐高频组件可以直接拿现成的改。第三是状态管理和工程化。原生小程序要管理全局购物车数据你得自己处理globalData再配合事件广播或者页面参数传递链路一长就容易乱。uniapp里可以直接用Vuex或者Pinia做全局状态管理购物车数据天然跨页面共享代码可维护性强很多。当然uniapp也不是没有缺点。它的编译层会引入一些额外运行时逻辑导致包体比原生略大性能上极端的动画场景会有轻微损耗。但对点餐这种轻交互场景来说这个问题可以忽略。1.3 这套源码的完整功能清单这套源码覆盖了奶茶店点餐的核心闭环具体包括微信授权登录与手机号绑定首页门店信息、公告和商品分类展示商品列表、商品详情、规格选择弹窗大杯/中杯、糖度、冰量、加料购物车底部浮层、角标、修改数量、清空、购物车列表页下单确认页选择取餐方式、填写备注、订单金额计算模拟支付与真实微信支付双模式切换订单列表与订单详情页支持查看订单状态个人中心会员信息、优惠券入口、地址管理自定义分享好友和分享朋友圈功能本地mock数据与真实后端接口一键切换这个范围基本能满足一家中小型奶茶店从开业到稳定运营的点单需求。如果你有源码可以直接在此基础上改门店名称、商品、价格和图片就能上线。2. 项目目录结构与点餐主流程的数据流转2.1 工程目录拆解每个文件夹到底放什么拿到源码第一件事不是急着跑起来而是先看懂目录结构。一个混乱的目录会在后期维护时让你痛不欲生尤其是一个人维护小程序时命名规范和目录规划比代码本身更重要。这套源码的目录结构是经过两次重构后定型的核心如下project-root/ ├── pages/ // 页面文件 │ ├── index/ // 首页/商品列表页 │ ├── cart/ // 购物车独立页 │ ├── order-confirm/ // 下单确认页 │ ├── order-list/ // 订单列表页 │ ├── order-detail/ // 订单详情页 │ └── mine/ // 个人中心 ├── components/ // 公共组件 │ ├── sku-popup/ // 商品规格选择弹窗 │ ├── cart-bar/ // 底部购物车栏 │ ├── goods-card/ // 商品卡片 │ └── empty-state/ // 空状态占位 ├── store/ // Vuex状态管理 │ ├── modules/ │ │ ├── cart.js // 购物车模块 │ │ └── user.js // 用户模块 │ └── index.js ├── api/ // 接口层统一封装 │ ├── request.js // 请求核心 │ ├── product.js // 商品相关接口 │ ├── order.js // 订单相关接口 │ └── user.js // 用户接口 ├── utils/ // 工具函数 │ ├── format.js // 金额/时间格式化 │ └── storage.js // 本地缓存操作 ├── static/ // 静态资源 │ ├── images/ │ └── tabbar/ ├── config/ │ └── index.js // 全局配置 ├── App.vue ├── main.js ├── manifest.json └── pages.json这个结构最核心的思路是页面只负责渲染和交互业务逻辑尽量收敛到store和api层。比如购物车里的加购、减购、清空、金额计算全部放在store/modules/cart.js页面里只调用this.$store.dispatch(cart/addItem, payload)。这样当你后续要加“再来一单”或者“购物车商品失效校验”时只需要改store层不会牵扯页面代码。2.2 商品列表到购物车的状态管理推荐饮品列表是点餐入口它的设计直接影响整个点单流程。首页顶部是门店信息和轮播图下面是分类导航左侧纵栏右侧是商品列表。这个左右联动布局在奶茶点餐里几乎成了标配因为商品分类多招牌、奶茶、果茶、纯茶、加料顾客需要能快速跳转。左右联动的核心是滚动监听。左侧点击分类右侧滚动到对应区块右侧滚动时左侧高亮当前分类。实现方式是用scroll-view的scroll-into-view和scroll事件配合分类区块的高度、索引、id映射关系需要实时计算。实际项目中常见的坑是手机端滚动事件触发频率过高导致性能问题解决方法是做节流处理每隔50毫秒计算一次当前分类索引。购物车数据在全局使用Vuex管理核心模块逻辑如下// store/modules/cart.js const state { items: [], // [{ productId, skuId, name, specText, price, count, image }] selectedShopId: } const getters { totalCount: state state.items.reduce((sum, item) sum item.count, 0), totalPrice: state state.items.reduce((sum, item) sum item.count * item.price, 0) } const mutations { ADD_ITEM(state, item) { const exist state.items.find(i i.skuId item.skuId) if (exist) { exist.count item.count } else { state.items.push(item) } } } const actions { addItem({ commit }, payload) { commit(ADD_ITEM, payload) } }为什么用skuId作为购物车条目的唯一标识因为同一个商品在“中杯/半糖/少冰”和“大杯/全糖/正常冰”两种规格下价格和备注都不同必须当作两个独立的购物车条目。用skuId关联后修改数量、删除、结算都会自动遵循规格维度。所有购物车数据同时会写入本地缓存这样用户杀掉小程序重新进入时购物车还在。缓存键建议包含门店ID否则用户换了门店之后可能把A店的购物车带到B店去结账这是个很容易忽略但实际很影响体验的bug。2.3 下单结算与订单状态机的设计下单确认页是全流程中数据链路最长的一页。它需要展示购物车里所有商品、计算总价商品总额包装费-优惠、选择取餐方式自取/堂食/外卖、填写备注最后提交订单。金额计算我强烈推荐在提交订单那一刻重新计算而不是直接信任前端购物车里的金额。原因很现实用户在购物车页面停留期间后台可能调整了商品价格或者某些商品已经下架。如果前端直接提交旧价格后端不做校验会造成金额不一致。真实的做法是前端把商品id和skuId列表提交给后端由后端重新计算价格返回给前端展示用户确认后再拉起支付。这套源码里为了演示方便前端也实现了金额计算但接真实后端时建议以后端计算为准。订单状态机的设计同样重要点餐场景的订单状态流转是单向的待支付 - 已支付/制作中 - 待取餐 - 已完成 - 已取消用户主动取消或超时未支付订单列表页轮询还是用WebSocket推送取决于店铺规模。中小型店面用定时轮询就好每10到15秒请求一次订单状态更新成本低实现简单。如果后续要做取餐叫号、多端实时同步再考虑WebSocket方案。3. 核心页面实现细节从sku选择到购物车角标3.1 商品规格大小杯/糖度/冰量选择器的实现思路规格弹窗是点餐小程序里交互最重要的组件也是用户最容易操作失误的地方。一杯奶茶的规格维度通常有三个杯型中杯/大杯、糖度全糖/半糖/少糖/无糖、温度热/温/冰/去冰。部分门店还有加料选项比如珍珠、椰果、奶盖每种加料还会影响最终价格。规格数据的组织方式决定了后端同学给你接口数据的复杂程度也决定了前端遍历逻辑的清晰度。我用的是简单的JSON数组嵌套结构// 商品规格数据模型 skuData: { specs: [ { name: 杯型, values: [中杯, 大杯] }, { name: 糖度, values: [全糖, 半糖, 少糖, 无糖] }, { name: 温度, values: [热, 温, 冰] } ], // sku列表每条sku对应一种规格组合 skus: [ { skuId: sku_001, specValues: [中杯, 半糖, 冰], price: 12, stock: 100 }, { skuId: sku_002, specValues: [大杯, 半糖, 冰], price: 15, stock: 80 } ] }前端拿到这个数据后要做两件事。第一渲染规格选择面板每个规格一组用户点击选中某个值。第二根据当前已选中的规格组合找到对应的sku显示实时价格和库存。如果某组规格值组合后没有对应sku说明这个组合不存在需要把对应的规格按钮置灰。这里有个体验细节规格选择的默认值应该设置成“门店最受欢迎的配置”比如“中杯、半糖、冰”这样大部分用户不需要额外操作就能直接加购减少操作步骤。如果是热饮还需要根据当前季节调整默认温度。加料逻辑单独处理因为加料是独立的选项集合不影响sku价格基础价但在总价上累加。数据结构上加料可以作为单个sku的扩展字段比如addons: [{ name: 珍珠, price: 2 }, { name: 椰果, price: 2 }]。用户选择加料后前端把加料部分也一起传给订单接口。3.2 购物车的浮层设计、角标联动与本地缓存策略购物车在点餐体验里是一个非常典型的“高频操作但空间有限”的场景。大多数奶茶店点餐首页购物车以底部浮层的形式存在点击后向上弹出一个半屏面板展示已选商品清单。底部浮层的设计有几个细节值得注意购物车角标在商品列表页和规格弹窗里都会实时变化每次添加或减少商品后角标数字需要同步更新。这里直接用Vuex的响应式状态即可但要注意一个性能问题角标数字变化会触发页面重渲染如果商品列表页同时存在很多图片资源会引起明显的卡顿。解决办法是角标单独抽成一个组件并且用计算属性做数量汇总减少不必要的渲染。购物车面板里的商品行需要支持左滑删除、数量加减这里的数量加减要和底部浮层的总价同步。加减商品时同一skuId的条目数量做即时增减价格即时计算不需要额外请求。只有当用户点击“去结算”时才进入下单确认流程。缓存策略上购物车数据必须持久化到本地。uniapp里可以用uni.setStorageSync但更规范的做法是封装一个storage.js工具统一处理读取、写入、删除的逻辑并且支持监听变化。我的实现是在购物车模块里加了一层watch任何购物车变化都会自动同步到本地缓存store.subscribe((mutation, state) { if (mutation.type.startsWith(cart/)) { uni.setStorageSync(cart_ state.cart.selectedShopId, JSON.stringify(state.cart.items)) } })注意缓存键里加上了门店ID防止用户切换门店后把购物车带过去这是我踩过坑后总结出来的关键点。3.3 模拟支付与订单列表的完整链路源码默认包含两种支付模式模拟支付和真实微信支付。模拟支付的目的是方便开发者本地调试和做Demo演示不需要真实商户号也能走完整下单流程。实现方式很简单在下单确认页点击“提交订单”后先判断全局配置里的支付模式// config/index.js export default { payMode: mock, // 可选值mock 或 real mockPayDelay: 800 // 模拟支付延迟时间模拟网络请求 }如果payMode是mock前端直接进入“已支付”状态同时创建一条本地订单记录如果是real调用uni.requestPayment拉起微信支付。这里要注意真实环境的支付流程是先调用后端下单接口后端返回支付参数前端拿到参数后调起支付。支付结果以后端的异步通知为准不能只依赖前端的成功回调。订单列表页需要处理的状态有待支付、制作中、待取餐、已完成、已取消。每个状态对应不同的卡片样式和操作按钮。比如待支付状态显示“去支付”和“取消订单”按钮制作中状态显示预计等待时间和订单编号待取餐状态显示取餐码这个取餐码可以做成大号字体方便店员叫号。订单详情的金额明细也要注意要展示商品金额、包装费、配送费、优惠金额、实付金额。包装费和配送费要单独列出来否则用户会产生疑问。后端返回订单数据时这些字段都应该清晰分列。4. uniapp编译到微信小程序后必须处理的适配坑4.1 顶部导航栏高度与胶囊按钮的兼容计算用uniapp开发微信小程序时导航栏的适配问题几乎每个项目都会遇到尤其是点餐首页这种需要自定义导航栏嵌入门店地址、搜索框的场景。微信小程序的导航栏与其他小程序不一样右上角有一个胶囊按钮它的位置和尺寸不固定会随着机型变化。官方提供了uni.getMenuButtonBoundingClientRect()来获取胶囊按钮的位置信息但很多初学者不知道需要用它来计算导航栏高度。我实际验证过一套非常稳定的计算方式// utils/navbar.js export function getNavBarHeight() { const menuRect uni.getMenuButtonBoundingClientRect() const systemInfo uni.getSystemInfoSync() // 导航栏实际高度 状态栏高度 (胶囊顶部到状态栏底部的距离 * 2) 胶囊高度 const navBarHeight (menuRect.top - systemInfo.statusBarHeight) * 2 menuRect.height return { statusBarHeight: systemInfo.statusBarHeight, navBarHeight, menuRect } }计算公式的原理是胶囊按钮上下留白是对称的这个留白距离就是menuRect.top - systemInfo.statusBarHeight用它乘以2再加上胶囊本身高度就是自定义导航栏的总高度。这个值在页面里用的时候建议通过Vuex或者mixins存起来不要每个页面都重新计算一遍。否则页面切换时导航栏高度会在短时间内闪烁跳动体验很差。另外还要提一个坑在iPhone X以上机型上底部有Home Indicator横条页面底部购物车栏要留出安全区的高度否则“去结算”按钮会被Home条遮挡。uniapp里可以用env(safe-area-inset-bottom)或者直接给底部容器加padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);。4.2 单选框、checkbox自定义样式在真机上的差异点餐流程里最典型的单选框场景是“取餐方式”的选择自取、堂食、外卖。很多人直接用了原生radio-group和radio结果真机上一测就发现问题。微信小程序原生的radio组件在不同机型上的样式表现不一致。部分安卓机型渲染出来异常大或异常小iOS上就正常这是因为微信小程序的radio组件在不同基础库版本里对样式的支持不同。还有一个问题是原生的radio选中态样式只能用radio-color来改颜色想要自定义选中图标非常困难。我的建议是不要用原生radio直接用view模拟单选。用当前选中项的值和一个selectedIndex比较判断是否显示选中样式配合CSS做一个圆形的外边框和内部实心点顺便加上一个勾选动画。这样在iOS和安卓上表现完全一致而且可定制度极高。另外在规格选择弹窗里规格值的选中态本质上也是单选框逻辑同样建议用纯view模拟不要用radio。我见过很多新手在规格这里套原生radio结果就是美观度和交互体验大打折扣。4.3 分包与异步化的配置以及manifest.json里那些容易漏掉的项当点餐小程序的代码量逐渐增加主包体积超过2MB时微信小程序的编译就会报错。解决方式是分包加载。在uniapp里配置分包需要在pages.json里声明subPackages字段{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 点餐 } } ], subPackages: [ { root: packageOrder, pages: [ { path: order-confirm/order-confirm, style: { navigationBarTitleText: 确认订单 } }, { path: order-list/order-list, style: { navigationBarTitleText: 我的订单 } } ] } ] }分包的原则是核心启动页面首页、购物车放在主包低频页面订单列表、个人中心、关于我们放到分包。这样用户打开小程序时主包体积更小加载速度更快。分包的异步化在微信小程序里有两个场景很重要。一是“分包异步化”这个优化项官方提供的lazyCodeLoading: requiredComponents配置可以让渲染层按需注入组件代码大幅减少首屏渲染时间。这个配置在manifest.json的mp-weixin节点里开启{ mp-weixin: { appid: 你的小程序AppID, setting: { urlCheck: false }, usingComponents: true, lazyCodeLoading: requiredComponents } }另一个场景是页面里引用分包组件时需要用require.async或微信的分包异步化语法来动态加载。在uniapp里大部分情况下把组件放到分包中再配合Vue的异步组件机制就能实现。manifest.json里还有一个高频漏配置项mp-weixin下的permission字段。如果要使用地理位置功能比如外卖点单需要定位到顾客地址必须在manifest.json的mp-weixin节点声明权限和requiredPrivateInfos。很多人在开发者工具里测试没问题到真机上定位就一直失败或弹授权框大概率就是这里没配置。{ mp-weixin: { permission: { scope.userLocation: { desc: 您的位置信息将用于确认配送地址 } }, requiredPrivateInfos: [getLocation] } }5. 后端接口对接与本地mock的无缝切换5.1 接口层封装request.js怎么做baseUrl切换这套源码的前后端分离设计里前端只需要关心接口定义后端地址通过配置切换。实际开发中最常见的问题就是开发环境用本地后端、测试环境用测试后端、生产环境用线上后端这三个地址来回切。如果每个页面写死请求地址改起来就是灾难。我的实现是在api/request.js里封装一个统一的请求方法基于uni.request核心逻辑如下import config from /config/index.js const request (options) { return new Promise((resolve, reject) { uni.request({ url: config.baseUrl options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) ? Bearer ${uni.getStorageSync(token)} : }, success: (res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data) } else if (res.statusCode 401) { // token过期处理 uni.removeStorageSync(token) uni.navigateTo({ url: /pages/login/login }) reject(res) } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res) } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) } export default request在config/index.js里通过环境变量切换baseUrluniapp支持条件编译所以我是这么写的// config/index.js let baseUrl https://api.example.com // #ifdef H5 baseUrl /api // H5端走代理避免跨域 // #endif // #ifdef MP-WEIXIN baseUrl https://api.example.com // 小程序端直接请求线上域名 // #endif export default { baseUrl, payMode: mock // 开发阶段用mock支付正式上线改成real }关键点是H5端用代理路径避免跨域小程序端用真实域名。因为小程序端不支持本地开发时用localhost请求除非在开发者工具里关闭域名校验但真机预览必须用线上域名。5.2 登录态与token的存储方案点餐小程序需要用户信息来记录订单、接收取餐通知所以登录环节必不可少。微信小程序的登录推荐用uni.login获取code然后后端拿着code去微信接口换openid和session_key再返回自定义登录态token。前端拿到token后存在本地缓存里后续请求自动带上。// api/user.js import request from ./request.js export function wxLogin() { return new Promise((resolve, reject) { uni.login({ provider: weixin, success: async (loginRes) { try { const res await request({ url: /auth/wxLogin, method: POST, data: { code: loginRes.code } }) uni.setStorageSync(token, res.token) uni.setStorageSync(userInfo, res.userInfo) resolve(res) } catch (e) { reject(e) } }, fail: (err) { reject(err) } }) }) }不要在前端直接调用uni.getUserProfile获取用户手机号和昵称微信在这方面的风控越来越严格正确做法是使用open-typegetPhoneNumber的button组件来获取手机号或者干脆只做静默登录等用户主动授权再补充信息。点餐场景里强烈建议不要启动时就弹授权框否则很容易造成用户流失。等用户提交订单或者查看历史订单时再引导授权转化率会好很多。实际开发中我还发现一个细节本地缓存的token有有效期后端返回的过期时间可能是一个时间戳。我建议在前端保存token刷新逻辑或者至少在后端返回401时做统一的重新登录跳转。上面request.js里已经写了401处理但注意要避免死循环——如果用户跳转到登录页后再次请求还是401那说明后端账号真的有问题需要提示错误信息而不是一直跳转。5.3 从mock数据无缝迁移到真实后端开发阶段为了方便演示不少源码会在前端内置一套mock数据商品列表、分类、订单全是写死的。这套模式在Demo阶段没问题但接入真实后端时如果处理不好会浪费大量时间。我推荐的做法是做一层数据层抽象所有页面和组件里只调用api层的方法永远不直接读mock数据。mock数据和真实数据的差异通过config/index.js里的useMock配置来控制let useMock false // #ifdef H5 useMock true // H5开发阶段开启mock // #endif export default { useMock }然后在api/product.js里同时保留两种实现import request from ./request.js import mockData from /mock/product.js import config from /config/index.js export function getProductList(params) { if (config.useMock) { return Promise.resolve(mockData.getProductList(params)) } return request({ url: /product/list, method: GET, data: params }) }这种方式下当你从mock环境切换到真实环境只需要改config/index.js里的useMock为false页面和组件代码完全不用动。这个模式在团队协作时特别有用前端可以先基于mock数据开发页面等后端接口就绪后直接切换互不阻塞。真正接真实后端时有几个小坑提前说下。一是商品图片的域名小程序要求图片域名必须是HTTPS且在后台配置了downloadFile合法域名否则图片完全加载不出来。二是后端返回的时间格式小程序端在iOS上对2025-01-01 12:00:00里的短横线解析有兼容问题最好统一返回时间戳或者ISO格式。三是接口返回的分页数据结构推荐和前端约定好统一的{ list, total, page, pageSize }结构避免每个接口都不一样。6. 上线前要做的检查与常见发布问题6.1 微信开发者工具里的白屏问题排查网上搜索“uniapp 微信小程序 手机上预览正常 开发者工具白屏”能看到大量类似问题。我一开始也遇到过现象是在微信开发者工具里打开项目页面完全白屏console没有任何报错但在真机上扫码预览一切正常。这个问题的根源通常是开发者工具和uniapp编译产物之间的兼容性问题常见触发原因有三类。第一类是微信开发者工具的“将JS编译成ES5”开关没有开启。uniapp编译出来的代码在较老的基础库版本上运行时如果工具没有开启ES5转换部分低版本手机或工具模拟器就会白屏。解决方法是在微信开发者工具的“详情 - 本地设置”里勾选“将JS编译成ES5”。第二类是项目启用了分包懒加载但开发者工具的基础库版本太低不支持lazyCodeLoading配置导致组件加载逻辑异常。这种情况把基础库版本切换到最新或者临时注释掉lazyCodeLoading配置就能验证是否这个原因。第三类是页面里使用了浏览器对象比如window、document在小程序环境里不存在导致运行时抛错但错误却被工具的静默处理吞掉了。排查技巧是在工具控制台输入vConsole或者打开“真机调试”看看Console面板是否有被吞掉的JS错误。我自己最终定位白屏问题时最有效的办法是在App.vue的onLaunch里加一行console.log(App launched)如果这行不打印说明根级别就挂了如果能打印但页面还是白的再检查页面组件内部渲染。一层层缩小范围比盲目搜索效率和体验都要好。6.2 自定义分享好友与分享海报的配置点餐小程序天然适合做分享裂变——顾客把一杯好喝的奶茶分享给朋友朋友点单后双方获得优惠券。微信小程序的自定义分享通过onShareAppMessage和onShareTimeline实现。在uniapp里页面定义onShareAppMessage就可以自定义分享标题、图片和路径export default { onShareAppMessage() { const cartCount this.$store.getters[cart/totalCount] return { title: cartCount 0 ? 我在XX奶茶点了一杯${this.currentProductName}你也来一杯吧 : 这家奶茶超好喝快来试试, path: /pages/index/index?inviteCode123456, imageUrl: this.shareBannerImage } }, onShareTimeline() { return { title: 这家奶茶店我必须推荐, query: inviteCode123456, imageUrl: this.shareBannerImage } } }这里有两个关键点。第一path里带上邀请码参数落地页解析参数后展示欢迎语或者发放优惠券。第二分享图片的尺寸建议设计成5:4比例这个比例在微信里展示效果最自然。很多点餐小程序还会在支付完成页生成一张分享海报海报上包括商品图、价格、二维码、门店信息用canvas绘制后保存到相册。canvas绘图的坑在于不同机型的像素比不一样图片会模糊。解决办法是用uni.getSystemInfoSync()拿到pixelRatio绘制时所有尺寸乘上pixelRatio再除以2这样在2倍屏和3倍屏上都能保持清晰。6.3 软著申请与上架安卓应用市场时的注意事项如果你的奶茶店点餐小程序后续不只想上微信还要上安卓应用市场比如华为、小米、OPPO那就绕不开软件著作权登记软著的申请。软著申请需要准备两份材料源代码文档和软件说明书。源代码文档的规范是提交前30页和后30页每页不少于50行如果整体代码不足60页就全部提交。说明书一般是操作界面截图加功能描述10页左右就够。提交到版权保护中心后正常流程大概需要1到2个月下证加急的话两周内。有了软著之后上架安卓市场还需要几个东西安卓应用签名、应用包名、隐私政策。签名可以用Android Studio生成也可以在DCloud的开发者中心用云打包生成。包名一般保持和manifest.json里的appid一致。关于上架流程uniapp的云打包确实方便但对于点餐这类涉及网络请求、支付、地图的App云打包还要特别注意权限声明配置。很多安卓市场审核人员比较严格如果你的应用声明了短信权限或读取通讯录权限但没有实际使用场景会被驳回。在manifest.json的Android权限配置里只勾选必要权限不需要的权限全部不要勾。微信支付和App支付还不一样。微信小程序里用的是“微信小程序支付”接口安卓App里用的是“App支付”接口两者申请的商户号相同但配置参数和调起方式不同。如果你同时做小程序和App别忘了在商户平台分别配置支付授权目录和回调域名。最后再分享一个小技巧。上架审核如果遇到“缺少隐私政策”或“隐私政策内容不符合要求”的驳回最简单有效的做法是把隐私政策做成一页独立页面放在小程序或App的“关于我们”里。内容包括我们收集哪些信息昵称、头像、手机号、订单信息、为什么收集这些信息用于下单、配送、客服、我们如何保护这些信息、用户如何注销账号。注销功能也很重要很多市场审核会专门检查。我做这套点餐源码最大的体会是技术难点其实不在某个孤立功能而在把微信生态的各种限制、真机差异、后端约定这些零零散散的细节串起来。很多问题看着是某一个页面挂了、某一段代码报错实际上根源都在配置、打包、审核链路里。希望这篇文章能把你在独立开发和上线过程中可能会遇到的大坑提前堵上让你少走一些我走过的弯路。本文还有配套的精品资源点击获取