
基于微信小程序的甜品设计——毕设源码实战说起微信小程序这两年的处境挺微妙的——你说它饱和了吧校园里点餐、宿舍里拼单、社团里报名还在满屏用你说它过气了吧随便一个本地甜品店、烘焙工作室用小程序做预约点单单量比APP还稳。我自己带过好几轮毕业设计甜品主题的小程序选题几乎年年有人选原因很简单业务闭环完整、页面承载量适中、后端接口边界清晰特别适合本科生在有限周期内做出一套逻辑自洽、可演示、可答辩的系统。这篇文章就围绕微信小程序甜品设计毕设来做完整拆解。不管你是自己选了类似题目还是拿到一份带源码的项目想二次开发我都会把这些年实操中踩过的坑、总结出的套路以及代码层面真正该注意的点一并讲清楚。源码版本的工程我在本地跑通过目录结构、接口设计、流程逻辑都会在下文直接拆开来讲。先说清楚这篇内容解决什么问题第一帮你理解一套甜品小程序从需求到落地的全链路设计第二把核心流程——点单、购物车、订单、核销、评价——逐个拆开讲透第三给出可直接复现的技术方案前端用微信小程序原生框架后端配合 Spring Boot 或 Node.js数据库用 MySQL第四整理答辩时导师最爱问的问题和应对思路。无论你是准备开题的大三学生、正在赶进度的准毕业生还是想拿这套项目做二次开发的开发者这篇文章都会给你比源码本身更多的价值。源码只告诉你“怎么跑”这篇会告诉你“为什么这么设计”。1. 项目整体设计与选题思路拆解甜品小程序这个题目我在学生项目里见过不同的做法。有的做成纯展示型只有商品列表和详情点单靠电话预订有的做成完整电商闭环会员、积分、优惠券全上。从带毕设的经验看中间路线最划算既有电商的完整骨架又不至于陷入营销系统的泥潭。为什么甜品适合做小程序而不是 H5 或者 APP三个原因。第一场景匹配度高。甜品消费是典型的“即时决策、附近推荐、到店即取”场景用户看到图片想下单下单后希望尽快拿到。小程序免安装、轻量打开、可以拿到地理位置天然契合“看到—点单—到店自提或配送”的路径。第二业务复杂度适中。一张甜品菜单大概20到40个SKU分类不过三五组没有服装电商那种尺码颜色多维组合也没有生鲜电商的库存批次管理。这个复杂程度正好落在一个本科生能掌控的范围内。第三答辩展示效果好。小程序可以在微信开发者工具里直接跑也可以用预览码在手机上演示考核老师扫码就能看到完整流程。相比之下纯后端管理系统或者PC网页的现场演示效果会弱不少。1.1 核心需求拆解以我手上的这套源码为例需求可以拆成三端、六个核心模块。三端是指用户端微信小程序顾客使用核心是浏览、点单、支付、查看订单、评价。管理端Web后台店主使用核心是商品管理、订单管理、分类管理、数据统计。服务端接口层衔接两端提供 RESTful API处理鉴权、业务逻辑、数据持久化。六个核心模块如下模块用户端能力管理端能力核心表用户模块微信登录、个人中心、收货地址用户列表、状态管理user商品模块分类浏览、商品列表、详情商品增删改查、上下架category, product购物车模块加购、修改数量、结算——cart订单模块下单、支付、取消、确认收货订单列表、发货/核销orders, order_item评价模块评价、晒图评价查看/回复comment营销模块可选优惠券领取、使用优惠券发放coupon为什么购物车不单独设计一张复杂的用户维度表因为甜品订单量不大购物车可以做成用户维度的持久化表也可以直接放到前端缓存Storage里。我手上的源码用的是前端缓存实现后端没有购物车表。这样做的好处是接口少、逻辑简单坏处是换设备购物车会丢。毕业设计能演示就好选前端缓存完全够用。1.2 技术选型背后的取舍逻辑先看这套源码的技术栈再解释为什么这么选前端微信小程序原生框架WXML WXSS JS JSON不用 uni-app 或 Taro。状态管理页面级 data 全局 app.globalData 本地 Storage。后端Spring Boot 2.x我手上这份是 Java 版也有 Node.js 版。数据库MySQL 5.7 / 8.0。鉴权方式wx.login 获取 code后端调用微信 code2Session 接口换取 openid自定义登录态 token 返回前端。HTTP 请求微信小程序自带 wx.request统一封装到一个 request 工具函数。很多人纠结为什么不用 uni-app一次编写多端发布不香吗我的看法是毕设场景下原生框架优于跨端框架。原因有三。其一微信开发者工具对原生小程序的调试体验最好编译速度快、报错信息直接指向 WXML/JS 的对应行uni-app 的报错要经过一层编译链排查成本更高。其二原生框架的文档和社区示例最多。你搜“微信小程序 购物车 左滑删除”原生写法一抓一大把换成 uni-app还要先折算成 uni 的语法和 API 风格。其三涉及微信原生 API登录、支付、订阅消息时原生框架直接调用不需要通过 uni 的封装层。尤其是微信支付uni-app 的支付流程在某些基础库版本上有兼容问题排查起来更麻烦。如果你已经选了 uni-app 也不用慌整体思路完全一样API 换成 uni.xxx 就行下面的业务设计照常参考。1.3 目录结构源码工程的骨架解读我把源码的目录结构重新梳理过一遍一个标准小程序工程的骨架长这样├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ ├── index/ # 首页商品分类列表 │ │ ├── category/ # 分类页部分版本融合在首页 │ │ ├── cart/ # 购物车 │ │ ├── order/ # 订单列表 订单详情 │ │ ├── user/ # 个人中心 │ │ ├── login/ # 登录页面授权引导 │ │ ├── address/ # 地址管理 │ │ ├── comment/ # 评价 │ │ └── search/ # 搜索 │ ├── components/ # 自定义组件数量选择器、商品卡片等 │ ├── utils/ │ │ ├── request.js # wx.request 统一封装 │ │ ├── util.js # 时间格式化等工具 │ │ └── config.js # 接口域名配置 │ ├── app.js # 全局逻辑 │ ├── app.json # 全局配置页面注册、tabBar │ └── app.wxss # 全局样式 ├── server/ # Spring Boot 后端 │ ├── src/main/java/com/xxx/sweet/ │ │ ├── controller/ # 接口控制器 │ │ ├── service/ # 业务逻辑 │ │ ├── mapper/ # mybatis-plus mapper │ │ ├── entity/ # 实体类 │ │ ├── config/ # 配置拦截器、跨域 │ │ └── common/ # 统一返回结果、异常处理 │ ├── src/main/resources/ │ │ ├── mapper/ # XML Mapper │ │ └── application.yml │ └── pom.xml └── sql/ # 数据库初始化脚本 └── sweet.sql这套结构虽然简单但每层职责非常清楚。需要提醒的是很多源码版本的目录命名不统一你拿到手第一件事不是急着跑起来而是先看目录对着上面的表把每块位置搞清楚后面改需求才知道往哪里改。2. 数据库设计与核心字段解析数据库设计是一套毕设项目的根基。我带学生改过太多项目前端写得花团锦簇结果一看数据库表结构要么缺关联关系要么时间字段用了字符串要么状态字段没有任何注释——答辩时一句话就被问住了。这套甜品项目的数据库涉及约10张表我重点讲几张核心表的字段设计和设计原因。2.1 用户表 userCREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid唯一标识, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, phone varchar(20) DEFAULT NULL COMMENT 手机号, gender tinyint DEFAULT 0 COMMENT 性别 0未知 1男 2女, status tinyint DEFAULT 1 COMMENT 状态 1正常 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里最关键的设计是openid 唯一索引。同一个微信号只能对应一个账号这是微信生态的天然属性。用户表不要自己搞用户名密码——微信小程序没有传统的账号密码概念身份以微信为准。时间字段要用 datetime而且 update_time 要设置自动更新。不少源码在这里偷懒用 varchar 存时间后期做订单排序、按天统计时全是坑。2.2 商品表与分类表分类表和商品表是经典的一对多关系。CREATE TABLE category ( id bigint NOT NULL AUTO_INCREMENT, name varchar(30) NOT NULL COMMENT 分类名称如慕斯蛋糕、布丁、饮品, sort int DEFAULT 0 COMMENT 排序字段越小越靠前, status tinyint DEFAULT 1 COMMENT 1显示 0隐藏, PRIMARY KEY (id) ); CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, category_id bigint NOT NULL COMMENT 所属分类ID, name varchar(100) NOT NULL COMMENT 商品名称, subtitle varchar(200) DEFAULT NULL COMMENT 商品副标题/简介, main_image varchar(255) DEFAULT NULL COMMENT 主图URL, detail text COMMENT 图文详情富文本, price decimal(10,2) NOT NULL COMMENT 价格单位元, original_price decimal(10,2) DEFAULT NULL COMMENT 划线原价, stock int DEFAULT 0 COMMENT 库存, sales int DEFAULT 0 COMMENT 销量, status tinyint DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;说两个细节。第一价格字段用 decimal(10,2)别用 float/double。二进制浮点数在价格计算上会有精度误差前端展示可能看不出问题但后端计算总价、做退款时精度损失就会暴露。这条我在看代码时会专门检查。第二商品不直接物理删除更通用的做法是软删除或状态控制。这套源码里没有 delete 字段直接通过 status 控制上下架简单够用。2.3 订单表与订单项表订单设计是整个项目里最重要的部分也是答辩必问的核心。表结构如下CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号, user_id bigint NOT NULL COMMENT 下单用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, pay_type tinyint DEFAULT 1 COMMENT 支付方式 1微信支付, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态 0待支付 1已支付/待核销 2已核销/已完成 3已取消 4退款, address_id bigint DEFAULT NULL COMMENT 配送地址ID自提可为空, take_type tinyint DEFAULT 0 COMMENT 0自提 1配送, remark varchar(255) DEFAULT NULL COMMENT 用户备注, pay_time datetime DEFAULT NULL COMMENT 支付时间, finish_time datetime DEFAULT NULL COMMENT 完成时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, product_id bigint NOT NULL, product_name varchar(100) NOT NULL COMMENT 商品快照名称, product_image varchar(255) DEFAULT NULL COMMENT 商品快照图片, price decimal(10,2) NOT NULL COMMENT 商品快照单价, quantity int NOT NULL COMMENT 购买数量, total_price decimal(10,2) NOT NULL COMMENT 小计金额, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么订单表要拆成 orders 和 order_item 两张表因为一个订单包含多个商品如果一张表存每个商品一行订单本身的维度——总额、支付状态、收货信息——就要重复存多遍冗余且容易不一致。为什么 order_item 要冗余 product_name 和 product_image因为商品信息会改名字会变价格会调。如果订单明细实时去关联商品表历史订单显示的名称和价格就是“当前”的商品信息而不是“下单时”的信息。这就是快照思路电商订单系统普遍这么做。答辩问“为什么商品改价后历史订单不变”靠这张表就能答。2.4 购物车为什么放进前端 Storage强调一下这套源码的购物车没有后端表而是用微信小程序的 Storage 存本地。实现逻辑是加购时把商品对象含 id、名称、图片、单价、数量、小计存到本地缓存的一个数组里key 设计为cart_${userId}。购物车页面每次 onShow 都从 Storage 重新读取保证数据最新。这么设计的理由是——购物车本质上是一个“未提交的意向”不属于核心业务数据。用户加购了十次都没下单这些数据存后端价值不大还增加接口压力。当然如果后续想扩展“购物车多端同步”就需要建购物车表重新设计毕设阶段不必。注意一个坑Storage 存的是商品对象数组如果后台调整了商品单价购物车里的旧单价不会自动更新。这里的兜底策略是进入结算页时前端购物车传给后端下单接口时后端必须重新从数据库读取价格计算总价而不是信任前端传来的价格。这个在后端下单逻辑里必须做到否则用户改一下请求参数就能以任意价格下单。这是安全设计里很重要的一条。3. 核心功能模块的实操实现这一章是全文的重头戏。我会把从用户登录到订单完成的核心链路逐个拆开结合源码里的关键代码段解析每个环节都说明业务设计原因和实现要点。3.1 微信登录与自定义登录态微信小程序登录流程是新手最容易写糊涂的地方。标准流程分两步。第一步小程序端调用wx.login()获取临时 code。wx.login({ success: (res) { if (res.code) { // 把 code 发给后端 wx.request({ url: ${config.baseUrl}/api/auth/login, data: { code: res.code }, success: (resp) { const { token, userInfo } resp.data.data; wx.setStorageSync(token, token); wx.setStorageSync(userInfo, userInfo); } }); } } });第二步后端拿着 code 换 openid。在 Spring Boot 里通过 Hutool 或原生 HttpClient 调用微信接口// 微信登录凭证校验 String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; // 响应包含 openid 和 session_key拿到 openid 后查表存在则直接登录不存在则自动注册新用户。然后生成一个自定义 token可以用 UUID 或 JWT存 Redis 或直接返回给前端后续请求带上 token后端通过拦截器验证身份。学生们最爱问的问题为什么不能直接拿 openid 当 token因为 openid 是用户的长期敏感身份标识一旦泄露别人可以伪装成该用户调用所有接口。自定义 token 相当于“临时门禁卡”有效期可控失效了重新登录即可安全性好得多。3.2 首页商品展示分类导航与商品列表首页整体布局是顶部搜索框中间横向分类导航可用 scroll-view 横向滚动下方商品瀑布流列表。分类导航的实现要点scroll-view scroll-x classcategory-bar view classcategory-item {{currentCategoryId 0 ? active : }} bindtapswitchCategory>// 计算选中商品总额 calcTotal() { const selectedItems this.data.cartList.filter(item item.selected); const total selectedItems.reduce((sum, item) { return sum item.price * item.quantity; }, 0); this.setData({ totalAmount: total.toFixed(2), selectedCount: selectedItems.length }); }, // 修改数量 changeQuantity(e) { const { id, type } e.currentTarget.dataset; const cartList this.data.cartList; const index cartList.findIndex(item item.id id); if (index -1) { if (type minus cartList[index].quantity 1) { cartList[index].quantity--; } else if (type plus) { cartList[index].quantity; } this.setData({ cartList }); this.calcTotal(); wx.setStorageSync(cart_${this.data.userId}, cartList); } }左滑删除是这个页面容易卡壳的地方通常用movable-view或手写 touch 事件实现。这套源码用的是更朴素的方案点删除按钮直接弹确认框。说实话如果是为了毕设左滑删除可以简化把精力放在订单流程上更值。3.4 确认订单与模拟支付从购物车到确认订单页核心工作有三块展示商品清单从上一个页面传来的商品列表或从 Storage 重新读取选中的项。选择配送方式自提或配送。配送需要关联地址管理。支付方式毕设项目一般做不到真实微信支付需要企业主体商户号源码里普遍做的是模拟支付——前端点击“支付”弹出确认框后端把订单状态置为“已支付/待核销”。模拟支付是个容易被质疑的点。很多学生的代码里支付接口直接改数据库状态没有任何延时和校验老师点“支付”的瞬间状态就变了显得有点假。我建议至少做成这样用户点击支付后小程序端弹出加载动画1.5秒然后调用后端模拟支付接口后端校验订单金额、订单归属更新状态为已支付并记录pay_time再返回支付成功。这样看起来更有业务流程感答辩时也能解释为生产环境这里对接微信支付统一下单接口毕业设计中用模拟支付替代以规避商户号资质问题。后端的模拟支付核心代码长这样PostMapping(/api/order/pay) public Result pay(RequestBody PayRequest req, RequestHeader(token) String token) { // 1. 校验token得到userId // 2. 查订单校验订单属于当前用户 // 3. 校验订单状态0待支付 // 4. 更新状态为1已支付记录支付时间和支付方式 // 5. 扣减商品库存、增加销量可异步 // 6. 返回支付成功 }3.5 订单状态机的设计新手常犯的一个错误是订单状态用if...else在代码里撒得到处都是改一个状态逻辑要找半天。正规做法是定义清晰的订单状态机在代码里用常量或枚举定义状态并规定允许的状态流转路径状态码状态含义可流转到0待支付1支付、3取消1已支付/待核销2核销完成、4退款2已完成无3已取消无4退款无后端在状态更新时统一走一个方法先校验当前状态是否允许流转到目标状态不允许就抛异常。这样设计的好处是无论从哪个入口用户取消、商家发货、系统超时触发状态变更都走同一套校验逻辑不会出现脏数据。这套源码里有一个地方我单独做过优化。比如用户下单后15分钟未支付自动取消可以用定时任务扫描超时订单但更轻量的做法是用户查询订单时如果发现订单是“待支付”且创建时间已超过15分钟前端直接展示“已超时取消”并调用后端接口把状态更新为取消。后者不用引入消息队列和定时任务适合学生项目。3.6 关于真实微信支付说点实在的讲真真实微信支付在毕设里基本走不通。原因很现实——微信支付商户号需要企业资质或个体工商户资质个人主体的小程序无法申请而且申请下来还要签约、配置证书、处理回调验签学生项目耗在这上面不值。所以毕设里的“支付”几乎清一色是模拟支付。但答辩时老师大概率会问“如果接入真实支付你怎么改”建议按这个思路回答前端调用wx.requestPayment传入从后端获取的支付参数timeStamp、nonceStr、package、signType、paySign。后端调用微信支付统一下单接口生成预支付交易会话标识 prepay_id并返回前端所需参数。用户支付成功后微信服务器会异步回调商户后端接口notify_url后端在回调里验签并更新订单状态。注意支付成功以回调为准不能只看前端返回结果。前端返回 success 只代表用户完成了支付输入真正资金到账需要等微信异步通知。这套回答讲出来老师就知道你理解真实支付链路而不是只停留在 demo 层面。4. 管理端后台商品与订单管理一套完整的毕设不能只有小程序端。管理后台是小程序的“另一半”也是体现全栈能力的重要部分。源码里管理端是用 Vue Element UI 写的 Web 页面连接同一个后端接口。4.1 管理端功能清单管理后台的功能不追求大而全但至少要有这些数据看板展示今日订单数、销售额、用户总数、待处理订单等统计卡片配合 ECharts 画一周销售趋势图。商品管理商品列表分页、搜索、新增/编辑商品上传图片、填写价格库存、上下架操作。分类管理分类的增删改查、排序。订单管理订单列表按状态筛选、订单详情商品明细、收货信息、发货/核销操作、退款操作。用户管理用户列表、查看用户订单记录。其中订单管理里的核销操作是甜品店场景独有的。自提订单用户到店后出示订单二维码商家输入核销码或在后台点“核销”订单状态从“已支付”变“已完成”。源码里实现得比较简——后台订单列表有一个“核销”按钮确认后状态置为2。如果你想做得更有亮点可以给订单生成二维码管理员用微信扫码核销。这个功能实现起来不算难但演示效果很加分。4.2 图片上传小程序与后台的通用方案图片上传是必做的功能。后台商品管理要传图小程序端用户评价要传晒图。方案上推荐直接用云存储或服务器本地存储。源码里用的是简单方案后端接收multipart/file上传保存到服务器本地目录返回可访问的 URL 路径。为了演示方便后端配置了静态资源映射spring: servlet: multipart: max-file-size: 5MB resources: static-locations: file:${upload.path}这样图片 URL 就可以直接通过http://localhost:8080/upload/xxx.jpg访问。部署到云服务器后把 upload.path 改成服务器目录图片外链就能正常展示。小程序端上传图片到后端的代码wx.chooseMedia({ count: 3, mediaType: [image], success: (res) { const tempFiles res.tempFiles; tempFiles.forEach((file, index) { wx.uploadFile({ url: ${config.baseUrl}/api/common/upload, filePath: file.tempFilePath, name: file, success: (uploadRes) { const url JSON.parse(uploadRes.data).data.url; // 把url存入评价图片列表 } }); }); } });一个容易踩的坑wx.uploadFile的返回结果是字符串不是对象必须先JSON.parse再取数据。不少新手在联调时卡在这里对照着找就能解决。4.3 后端统一返回与异常处理读源码时你会发现所有接口返回格式都是统一的{ code: 200, message: success, data: { ... } }这是后端Result类统一包装的。异常时返回{ code: 500, message: 库存不足, data: null }前端 request.js 里对所有响应做了拦截code 不为 200 时统一wx.showToast展示后端错误信息并 reject 掉。这样前端每个页面的业务代码里不用到处写 try-catch看起来干净很多。这个设计看起来简单但价值很大。它保证了前后端联调时错误信息的一致性和可排查性也是代码工程化的基本要求。答辩问“接口错误怎么处理”把这一套讲清楚很难被挑毛病。5. 前端关键交互与工程细节接下来讲小程序前端开发里那些“不写就不知道”的细节。这些内容通常不在需求文档里但直接决定用户体验和项目完成度。5.1 底部 tabBar 与页面注册一个甜品小程序通常有四个主 Tab首页、分类或购物车、订单、我的。// app.json { pages: [ pages/index/index, pages/category/category, pages/cart/cart, pages/user/user ], tabBar: { color: #999999, selectedColor: #FF6B81, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/home.png, selectedIconPath: images/home-active.png }, { pagePath: pages/category/category, text: 分类, iconPath: images/category.png, selectedIconPath: images/category-active.png }, { pagePath: pages/cart/cart, text: 购物车, iconPath: images/cart.png, selectedIconPath: images/cart-active.png }, { pagePath: pages/user/user, text: 我的, iconPath: images/user.png, selectedIconPath: images/user-active.png } ] } }tabBar 的 iconPath 有两个注意点图片需要是 PNG 格式且大小一般不超过 40kb图标尺寸建议在 81px * 81px 附近。超过大小限制小程序后台会提示上传失败图标带不透明底色在 tabBar 里可能显示成黑色方块。这些坑都是自己踩过才知道的。5.2 顶部导航栏适配微信小程序的顶部导航栏高度不是固定的。不同机型状态栏高度不同刘海屏、灵动岛这些机型状态栏更高。如果用自定义导航栏不做适配标题就会顶进状态栏。源码里的适配方案是// 获取状态栏高度 const systemInfo wx.getSystemInfoSync(); const statusBarHeight systemInfo.statusBarHeight; // 自定义导航栏高度 状态栏高度 44px默认导航栏高度在 WXML 里view classnav-bar stylepadding-top: {{statusBarHeight}}px; height: {{navHeight}}px; text classnav-title甜品小店/text /view这套适配方案在绝大多数安卓/iOS 机型上都能正常显示。如果直接用默认导航栏这些都不用操心但用了自定义导航栏这是必须处理的第一件事。5.3 列表加载更多与下拉刷新商品列表、订单列表都需要“上拉加载更多”和“下拉刷新”。“加载更多”的实现要点是分页参数管理data: { page: 1, pageSize: 10, hasMore: true, productList: [], loading: false }, // 页面触底时加载下一页 onReachBottom() { if (!this.data.hasMore || this.data.loading) return; this.loadProducts(); }, async loadProducts() { const { page, pageSize, productList } this.data; this.setData({ loading: true }); const res await request.get(/api/product/list, { page, pageSize, categoryId: this.data.currentCategoryId }); const list res.data.records; this.setData({ productList: page 1 ? list : productList.concat(list), hasMore: res.data.pages page, page: page 1, loading: false }); }这里的关键是concat而不是赋值——分页加载是在原有列表上追加不是替换。hasMore的判断依据是当前页是否小于总页数。后端用 MyBatis-Plus 的分页插件返回的数据结构自带records、pages、total这些字段前端直接取用即可。下拉刷新在页面 json 里声明{ enablePullDownRefresh: true, backgroundTextStyle: dark }然后在onPullDownRefresh里把 page 重置为 1重新请求第一页数据数据回来后调用wx.stopPullDownRefresh()关闭刷新动画。不少新手写完下拉刷新忘记调 stopPullDownRefresh加载动画一直转这个问题很好排查。5.4 微信开发者工具的使用与真机调试源码拿到手第一件事是用微信开发者工具导入项目。具体步骤打开微信开发者工具选择“导入项目”。目录选择源码里的 miniprogram 目录如果前端和后端分开就选前端目录。AppID 可以先用测试号也可以注册自己的小程序账号获取。导入后在“详情 - 本地设置”里勾选“不校验合法域名”——本地开发时接口是 http://localhost:8080微信默认只允许 https 和已备案域名。在utils/config.js里把 baseURL 改成你本机的局域网 IP例如http://192.168.1.102:8080这样手机预览时也能访问到电脑上的后端。真机预览的坑手机和电脑必须在同一个局域网内且手机访问电脑 IP 时不能被防火墙拦截。Windows 系统记得在防火墙里放行 8080 端口否则手机扫码预览后所有接口全部失败报错是request:fail。这个在带学生时遇到过太多次第一次碰到会非常困惑——开发者工具里一切正常真机就全挂。6. 二次开发指南拿到源码后该怎么做很多读者拿到的是压缩包解压后面对一堆文件无从下手。我把拿到源码后最高效的上手路径分成七步每一步都说明目的和容易踩的坑。6.1 步骤一环境准备与项目导入在开始之前先把环境装齐。我列一个清单组件版本建议用途JDK1.8 或 11运行 Spring Boot 后端Maven3.6管理后端依赖MySQL5.7 / 8.0数据存储Redis可选5.0token 缓存不用可跳过微信开发者工具稳定版运行小程序前端Node.js可选14如果后端是 Node 版后端导入 IDE 后先修改application.yml里的数据库连接信息数据库名、用户名、密码。然后用 Navicat 或命令行执行sql/sweet.sql把表结构和初始数据导入 MySQL。启动后端前需要注意如果源码里配置了 Redis 缓存登录态而本机没装 Redis启动会报错。解决方法是先启动本地 Redis或者把缓存逻辑改成 ConcurrentHashMap 的内存缓存——毕设项目够用。改内存缓存的代码量不大不需要引入 Redis 依赖很多学生版本就是这么做的。6.2 步骤二跑通接口联调后端启动后先用 Postman 或 Apifox 测一个最简单的接口比如GET /api/product/list能返回商品列表 JSON说明后端 OK。然后改前端utils/config.js里的 baseURL// 开发环境 const baseURL http://localhost:8080; // 真机预览时改成局域网IP // const baseURL http://192.168.1.102:8080;在开发者工具里把“不校验合法域名”勾上刷新小程序看到首页商品列表前后端就打通了。如果接口返回 401 或登录失败大概率是app.js的 onLaunch 里登录逻辑没跑通。在 Network 面板里看/api/auth/login的请求是否返回了 token以及后续请求头里是否带上了 token。这里推荐直接用开发者工具的 Network 面板比自己在代码里打 console.log 高效得多。6.3 步骤三改造需求的优先级建议如果你不想只做“开题答辩版”想把项目改成更有辨识度的作品建议按以下优先级改造P0建议改把默认商品数据换成你自己设计的甜品品牌、商品、文案。更换小程序界面主色调和 Logo让项目有视觉辨识度。修改app.json里的导航栏标题和window背景色。P1推荐加增加“今日推荐”或“人气榜单”模块在首页加一个横向滑动区域。增加订单倒计时显示下单后 15 分钟未支付自动取消的倒计时。给订单增加二维码核销功能后台扫码完成核销。P2有余力再加接入优惠券模块注册送券、下单用券后端加优惠券表和用户券表。接入图表统计用 ECharts 展示近 7 天的销售趋势管理端数据看板立刻专业起来。消息推送下单成功后通过订阅消息给用户发送订单状态通知。从毕设答辩角度讲P0 决定作品完整度P1 决定作品亮点P2 决定作品深度。建议至少完成 P0 全部和 P1 的一到两项。6.4 如何让答辩演示更出彩这是很多人忽视的部分。源码能跑只是及格分演示得好不好直接关系到最终成绩。我的经验是演示前准备一份脚本。按顺序展示以下场景注册/登录进入小程序自动登录展示用户信息。浏览商品首页分类切换、商品列表滚动、查看商品详情。加入购物车修改数量、选择商品、查看合计金额。提交订单选择配送方式自提/配送、填写备注、提交订单。模拟支付展示支付确认、支付成功、订单状态变化。切换管理后台展示订单列表核销刚才的订单。回到小程序刷新订单列表状态变为已完成补一条评价。这个脚本不只演示功能更重要的是展示数据联动的完整性。订单在小程序端下单、后台核销、前端状态同步变化老师会直观感受到这是一套完整系统而不是静态页面。答辩时把数据库表结构准备好。如果老师问到订单设计能快速打开 Navicat 展开 orders 和 order_item 表讲清楚主外键关系和快照字段。这种“手上真有东西”的状态比对着 PPT 念强十倍。7. 常见问题排查与避坑汇编下面这部分是我这几年带学生做毕设时遇到的高频问题汇总。这些问题我在源码调试过程中几乎都遇到过每一条都是拿时间换来的经验。7.1 微信开发者工具报错解析错误1app.json: 未找到 app.json 或文件内容格式错误原因导入项目时目录选错了。小程序工程目录必须直接包含 app.json而不是选到整个项目根目录根目录里可能有前端和后端两个文件夹。解决重新导入目录选择miniprogram文件夹。错误2wx.request 请求失败 request:fail原因域名未配置或本地网络不通。开发环境下先在“详情 - 本地设置”勾选“不校验合法域名”如果是真机预览检查手机和电脑是否同一局域网、防火墙是否放行端口。错误3TypeError: Cannot read property data of undefined原因后端返回格式和前端预期不一致。最常见的是后端返回了{code:200, message:success, data:{...}}前端却写了res.data.data而实际结构不同或者接口异常返回了空对象。解决办法是在 Network 面板里看实际响应结构再调整取值路径。错误4Failed to load image原因图片路径是http://localhost:8080/upload/xxx.jpg开发者工具能访问但真机访问不了——localhost 在手机上指向手机自己。解决把后端图片访问地址换成电脑局域网 IP或部署到云服务器后使用公网域名。7.2 后端常见异常异常1Access denied for user rootlocalhost原因MySQL 账号密码不对或远程访问权限没开。先在命令行用 MySQL 客户端测试连接mysql -uroot -p能进说明密码没问题再检查 application.yml 里的配置是否写对。异常2Table xxx doesnt exist原因数据库脚本没执行或连错了数据库。检查 MySQL 里是否有对应数据库和相关表。异常3端口被占用Spring Boot 默认端口 8080如果本机已有服务占用启动会报Port already in use。解决改application.yml的 server.port或关掉占用进程。异常4拦截器导致所有接口 401这是新手极易忽略的后端写了登录拦截器但没有放行/api/auth/login和商品列表等公开接口前端的 token 还没拿到其他所有请求全被拦截。解决在拦截器配置里明确放行白名单比如registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/product/list, /api/upload/**);7.3 数据库数据不一致问题问题1下单后库存没扣检查后端下单逻辑里是否校验库存并且执行了扣减。很多源码只在做了前端“库存不足”的提示后端却没有对应逻辑。需要确认orders表状态更新和product.stock扣减是否在同一个事务里。问题2取消订单后库存没回补和上一条配套订单取消或退款后应该把库存加回来。源码里如果没做建议补上这是答辩可能被追问的点。问题3订单金额和商品价格对不上原因可能有两个一是前端传给后端的金额被直接信任二是商品改价后旧订单明细还是旧价格。正确做法下单时后端从数据库读取商品最新价格重新计算总价并把快照写入 order_item。前端传的金额只作为展示参考不作为计价依据。这是我在代码审查时必讲的一条。7.4 用户体验细节自查清单在提交之前用这个清单快速自查一遍能避免很多低级扣分项支付按钮是否防止了重复点击用 isLoading 置灰购物车为空时是否展示了空状态文案而不是白屏商品库存为 0 时是否置灰“加购”按钮或提示“已售罄”网络请求失败时是否有 toast 或错误提示而不是默默失败提交订单成功后是否清空了购物车里已下单的商品订单列表下拉刷新后状态是否及时更新商品详情页返回时列表滚动位置是否保留最后这一条很多人会忽略。微信小程序的navigateTo跳详情返回时页面栈里的上一个页面 onShow 会自动触发列表位置默认保留。但如果你用了redirectTo或reLaunch把上一个页面关了返回时就会重新加载列表、丢失滚动位置。所以页面跳转选择navigateTo保留页面栈还是redirectTo关闭当前页要结合交互需求想清楚。8. 从毕设到真实项目扩展方向的个人思考前面聊的都是如何把甜品小程序做成一个合格的毕设。如果你拿到源码后不只是为了交差而是真的想把小程序做成上线产品有几个方向值得琢磨。第一从“能演示”到“能运营”。真实运营需要数据埋点能力统计每个页面的访问量、转化率跑通从“加购”到“支付”的漏斗数据。毕设项目基本没有埋点上线前需要接入数据分析平台或自建日志体系。第二从“模拟支付”到“真实支付”。前面说过真实支付需要企业主体小程序和微信支付商户号。走上了线这条路这步绕不开。微信支付 V3 的接入流程可以提前熟悉申请商户号、配置 API 证书、后端集成 SDK、处理回调。流程不难但比较繁琐。第三从“手动核销”到“自助取货”。思路很简单订单支付成功后在订单详情页生成取货码或二维码用户到店后商家扫码确认。这样可以避免高峰期人工核对订单号的低效。再进阶一点可以结合蓝牙打印机自动打印小票——这也是很多甜品店真实使用的场景。第四从“单店”到“多店”。如果单店模式验证成功下一步是复制到多个门店。多店的核心是增加 store 表、商品与门店关联、库存按门店隔离、订单绑定门店以及自提点逻辑。架构上其实是从单体应用走向多租户改造复杂度会明显上一个台阶。第五从“小程序”到“私域运营”。甜品店拼的不是一次性流量而是复购和客单价。小程序配合会员体系可以做得很深储值卡、积分商城、生日优惠券、社群裂变。这些功能落到后端无非是几张表和几个接口但产品逻辑的差异很大值得单独开篇细聊。我的态度是毕设只是起点源码只是脚手架。真正值钱的是你通过这些代码理解了一整套电商业务流转的能力——从用户登录到商品展示到下单支付再到订单履约。这套逻辑放到任何一个小程序商城上本质上都是一样的。9. 实操总结与个人经验分享做完这么多轮甜品小程序项目我最深的体会是毕设项目不追求技术多新颖追求的是逻辑自洽、流程完整、表达清楚。一套系统哪怕只用了增删改查只要每个环节都能自圆其说每个表设计都有理由每个接口都有异常处理就能拿到不错的成绩。具体到甜品小程序这个题有几个建议送给正在做或准备做的你。第一把精力优先放在订单流程上而不是界面的花哨程度。订单状态流转是这套系统的核心命脉演示时订单能流畅走完“下单—支付—核销—评价”的全链路比十个酷炫动画都管用。第二数据库表设计要能讲出理由。答辩时老师问“为什么订单明细要存商品快照”你如果能从历史数据一致性的角度回答再顺手展开到电商系统的通用设计这是很亮的加分点。第三遇到 bug 先看 Network 面板再 console.log。微信开发者工具自带的调试器非常强请求状态、响应数据、控制台报错一应俱全。把请求流程理清楚90% 的问题都能定位。第四不要盲目加功能。很多学生喜欢在毕设里堆功能优惠券、积分、秒杀、直播全想上结果每个模块都只做了一半演示时漏洞百出。与其多而杂不如少而精。一套甜品点单系统把商品、购物车、订单、评价四个模块做扎实已经是一份非常完整的毕设了。第五答辩 PPT 里放一张系统架构图。小程序端、后端、数据库三层的架构画清楚标注好请求流向老师一看就知道你脑子里有全局图景。这比贴一堆代码截图有效得多。最后再分享一个小技巧做演示前把数据库里的初始数据改成你自己的品牌内容。比如把商品名改成“芒果千层”“提拉米苏”“杨枝甘露”把店铺名改成你自己起的名字把公告改成欢迎语。这样哪怕代码是参考的演示效果也是属于你自己的作品。不少学生忽略了这一步评委看了多年的“甜品小店”一眼就能看出是模板套的。源码会给你一条已经铺好的路但你应该在路上留下自己的脚印。把项目理解透、改顺了它就不再是别人的源码而是你自己的作品。