ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序网上花店源码实战:从跑通到毕业设计改造

微信小程序网上花店源码实战:从跑通到毕业设计改造

简介:这是一套面向高校计算机相关专业学生的网上花店微信小程序毕业设计完整源码,采用Java后端与小程序前端分离架构,适合作为毕业设计、课程设计或小程序入门练手项目。系统区分管理员与用户两类权限:管理员可维护用户信息、发布公告与鲜花信息并与用户互动,用户则可浏览鲜花、查看公告及管理员回复,功能闭环完整。压缩包共1778个文件,涵盖vue、js、json等前端资源,java后端代码,wxss与wxml小程序页面,以及png、svg、jpg等图片素材,另附sql数据库文件与md说明文档,整体约36.19MB。运行环境为JDK1.8、MySQL5.7、Maven3.3与Tomcat7,前端使用HBuilderX或微信开发者工具,配套Navicat管理数据库。已有95人学习,读者可据此快速搭建可运行项目,理解前后端交互与权限控制逻辑,并在此基础上完成论文与答辩准备。

1. 网上花店小程序源码拆解:从跑通到改造成自己的毕业设计

很多同学拿到「基于小程序的网上花店微信小程序源码」这类压缩包时,第一反应是解压、用微信开发者工具打开、点编译,然后盯着满屏报错发呆。我当年做毕业设计也是这个流程,翻车过好几次才摸清门道。这个标题背后其实是一套完整的电商闭环:商品列表、详情、购物车、下单、订单管理,外加一个后台管理端,通常还会配一份 LW(论文)文档。它解决的核心问题是——让你不用从零写页面,而是站在一套能跑通的骨架上,把花店这个垂直场景改造成自己的东西。适合谁?适合计算机、软件工程、物联网方向的毕业生,也适合想快速上手微信小程序原生开发、又不想啃官方文档啃到崩溃的初学者。关键在于,你得先让它跑起来,再谈改造。

2. 拿到源码先别急着改:环境、目录与跑通顺序

2.1 微信开发者工具与基础环境确认

拿到压缩包,第一步不是解压完就双击打开。先确认你本机装的是微信开发者工具稳定版,登录的微信号已经开通了小程序开发者权限。没有 AppID 也能跑,用测试号即可,但测试号不支持真机预览的部分能力,比如获取手机号。这一点在毕业设计答辩演示时很关键,老师如果让你现场扫码,测试号会掉链子。

解压后你会看到典型的目录结构,常见的是这样:

├── miniprogram/ # 小程序前端代码 │ ├── pages/ # 页面目录 │ ├── components/ # 自定义组件 │ ├── utils/ # 工具函数 │ ├── images/ # 静态图片 │ ├── app.js # 全局逻辑 │ ├── app.json # 全局配置 │ └── app.wxss # 全局样式 ├── cloudfunctions/ # 云函数(如果用了云开发) ├── project.config.json # 项目配置 └── LW.docx # 论文文档

有些源码是前后端分离的,后端可能是 Java、PHP 或 Node.js,前端只留小程序部分。先看 project.config.json 里的 miniprogramRoot 字段,确认小程序代码到底在哪个子目录。我见过一个包,小程序代码藏在source/miniprogram下面,直接打开根目录会提示找不到 app.json,白白浪费半小时。

2.2 导入项目与首次编译的必改项

用微信开发者工具「导入项目」,目录选到包含 project.config.json 的那一层。导入后先别点编译,打开 project.config.json,把 appid 改成你自己的测试号或正式 AppID。如果你没有 AppID,在工具里选择「测试号」也行,但记得后面获取手机号、微信支付这些功能会受限。

{ "miniprogramRoot": "miniprogram/", "appid": "你的AppID或测试号", "projectname": "flower-shop", "setting": { "urlCheck": false, "es6": true, "enhance": true, "postcss": true, "minified": true } }

这里urlCheck必须设为 false,否则本地开发时请求后端接口会被域名校验拦住。es6和enhance打开,保证新语法能编译。改完保存,点编译。如果控制台报「app.json 未找到」,说明 miniprogramRoot 路径不对,回去检查目录层级。

首次编译常见的报错还有:缺少 node_modules、云函数未部署、后端接口 404。如果是云开发版本,右键 cloudfunctions 下的每个函数,选择「上传并部署:云端安装依赖」。如果是传统后端,找到后端项目,用 IDEA 或 VS Code 跑起来,再把小程序里utils/request.js或config.js里的 baseUrl 改成你本机的 IP 加端口。注意,本机 IP 不能写 localhost,真机调试时手机访问不到,要写局域网 IP,比如http://192.168.1.100:8080。

2.3 数据库导入与后台账号初始化

花店小程序一般会有商品表、分类表、订单表、用户表、购物车表。源码包里通常会带一个.sql文件。用 Navicat 或 MySQL 命令行导入:

mysql -u root -p flower_shop < flower_shop.sql

导入后检查表里有没有初始数据。很多源码的商品图片用的是外链,时间久了外链失效,首页会一片空白。这时候要么把图片下载到本地 images 目录,要么换成正则替换成自己的图床地址。后台管理端的默认账号密码一般在 SQL 文件末尾的 insert 语句里,常见的是 admin/123456。登录后台,先把轮播图、商品分类、几个测试商品配好,不然小程序首页没数据,你以为是代码坏了,其实是数据库空的。

提示:导入 SQL 前先建好同名数据库,字符集选 utf8mb4,否则中文商品名会乱码。

3. 花店核心页面的改造:商品、购物车与下单链路

3.1 商品列表与详情页的数据绑定

跑通之后,你最先想改的就是商品。花店小程序的商品列表通常在pages/goods/list或pages/index/index。数据来源是wx.request调后端接口,返回一个数组。核心代码长这样:

// pages/goods/list.js Page({ data: { goodsList: [], page: 1, pageSize: 10, hasMore: true }, onLoad() { this.loadGoods(); }, loadGoods() { const { page, pageSize } = this.data; wx.request({ url: `${app.globalData.baseUrl}/goods/list`, data: { page, pageSize }, success: (res) => { if (res.data.code === 200) { this.setData({ goodsList: this.data.goodsList.concat(res.data.data.list), hasMore: res.data.data.list.length === pageSize }); } } }); }, onReachBottom() { if (this.data.hasMore) { this.setData({ page: this.data.page + 1 }, () => this.loadGoods()); } } });

page和pageSize控制分页,onReachBottom做上拉加载。改造成花店时,你要把接口返回的字段映射到自己的表结构。常见坑是后端返回的图片字段是相对路径,前端直接塞进image标签的 src 会 404。解决办法是在 request 的 success 里拼上 baseUrl,或者让后端返回完整 URL。详情页pages/goods/detail接收id参数,调/goods/detail?id=xxx,然后把轮播图、价格、库存、描述渲染出来。花店场景要特别注意库存和规格,比如「红玫瑰 11 枝」和「红玫瑰 19 枝」是两个 SKU,价格不同,库存独立。

3.2 购物车逻辑与本地缓存策略

购物车是毕业设计里最能体现逻辑的地方。很多源码把购物车存在本地wx.setStorageSync,也有存后端的。本地缓存的好处是不用登录也能加购,坏处是换手机就没了。我一般建议毕业设计用本地缓存加后端同步的混合方案:未登录时存本地,登录后合并到后端。

// utils/cart.js const CART_KEY = 'cart_list'; function getCart() { return wx.getStorageSync(CART_KEY) || []; } function addToCart(goods, count = 1) { const cart = getCart(); const index = cart.findIndex(item => item.id === goods.id); if (index > -1) { cart[index].count += count; } else { cart.push({ id: goods.id, name: goods.name, price: goods.price, image: goods.image, count: count, checked: true }); } wx.setStorageSync(CART_KEY, cart); return cart; } function updateCount(id, count) { const cart = getCart(); const index = cart.findIndex(item => item.id === id); if (index > -1) { cart[index].count = count; wx.setStorageSync(CART_KEY, cart); } return cart; } module.exports = { getCart, addToCart, updateCount };

checked字段控制结算时选中哪些商品。总价计算时只累加checked === true的项。注意,价格用「分」做单位存整数,避免浮点数精度问题,展示时再除以 100。这个细节很多源码忽略,导致 0.1 + 0.2 算出 0.30000000000000004,订单金额对不上。

3.3 下单与订单状态流转

下单链路是:购物车结算 → 确认订单页 → 提交订单 → 订单列表。确认订单页要展示收货地址、商品清单、总价、备注。提交时调/order/create,后端生成订单号,返回 orderId,前端跳转到支付或直接跳订单详情。毕业设计如果没有接微信支付,可以模拟支付成功,把订单状态从「待付款」改成「待发货」。

订单状态一般有:待付款、待发货、待收货、已完成、已取消。后端用状态机管理,前端根据状态显示不同按钮。花店场景还要加一个「配送时间」字段,因为鲜花是时效性商品,用户可能指定某天某时段送达。这个字段在数据库里加一个delivery_timedatetime,前端用 picker 选择。

// pages/order/confirm.js submitOrder() { const { address, cartList, remark, deliveryTime } = this.data; if (!address) { wx.showToast({ title: '请选择收货地址', icon: 'none' }); return; } wx.request({ url: `${app.globalData.baseUrl}/order/create`, method: 'POST', data: { addressId: address.id, items: cartList.filter(i => i.checked), remark, deliveryTime }, success: (res) => { if (res.data.code === 200) { wx.redirectTo({ url: `/pages/order/detail?id=${res.data.data.orderId}` }); } } }); }

提交前一定要做空校验,地址没选、购物车为空都要拦住。我见过一个源码,购物车为空也能提交,后端生成一条金额为 0 的订单,数据库里一堆脏数据。

4. 登录、手机号与后台管理的对接细节

4.1 微信登录 code 换取 openid 的完整流程

微信小程序的登录不是账号密码,而是wx.login拿到临时 code,传给后端,后端用 code + appid + secret 调微信接口换 openid 和 session_key。前端代码:

wx.login({ success: (res) => { if (res.code) { wx.request({ url: `${app.globalData.baseUrl}/user/login`, method: 'POST', data: { code: res.code }, success: (resp) => { if (resp.data.code === 200) { wx.setStorageSync('token', resp.data.data.token); wx.setStorageSync('userInfo', resp.data.data.userInfo); } } }); } } });

后端拿到 code 后,请求https://api.weixin.qq.com/sns/jscode2session,参数是 appid、secret、js_code、grant_type=authorization_code。返回 openid 和 session_key。openid 是用户唯一标识,存到用户表。token 可以用 JWT 生成,前端每次请求带在 header 里。注意,session_key 不能下发给前端,它用来解密手机号等敏感数据。

4.2 获取手机号的 button open-type 写法

获取手机号需要用户点击按钮授权,按钮的open-type="getPhoneNumber",绑定bindgetphonenumber事件。事件回调里拿到encryptedData和iv,传给后端解密。

<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"> 获取手机号 </button>
getPhoneNumber(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { wx.showToast({ title: '您拒绝了授权', icon: 'none' }); return; } wx.request({ url: `${app.globalData.baseUrl}/user/decryptPhone`, method: 'POST', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, token: wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { wx.setStorageSync('phone', res.data.data.phone); } } }); }

后端用 session_key 做 AES-128-CBC 解密,得到手机号。这里有个坑:session_key 会过期,如果用户长时间没操作,解密会失败。解决办法是解密失败时重新走一遍wx.login刷新 session_key,再让用户点一次。另外,测试号不支持获取手机号,必须用正式 AppID,而且个人主体的小程序也没有这个权限,只有企业主体才行。毕业设计如果卡在这里,可以先用模拟手机号代替,答辩时说明即可。

4.3 后台管理端与小程序的数据一致性

后台管理端一般是 Vue + Element UI 或 React + Ant Design,跑在浏览器里。它和小程序共用同一个数据库。常见问题是后台改了商品价格,小程序端没刷新,因为小程序有缓存。解决办法是在后台保存时调一个接口,让小程序端的商品缓存失效,或者小程序端在 onShow 时重新拉取数据。

后台的商品编辑页要支持多图上传、富文本描述、规格设置。花店商品规格多,后台表单要能动态添加规格项。数据库设计上,商品表存基本信息,规格表存 SKU,用 goods_id 关联。订单表存订单基本信息,订单详情表存每个商品快照,包括下单时的价格和名称,防止商品改价后历史订单金额变化。

注意:后台管理端的接口权限要加 token 校验,不然任何人知道地址都能改数据。毕业设计虽然简单,但这是基本的安全意识。

5. 避坑与排查:毕业设计里最容易翻车的 5 个点

5.1 编译报错「未找到 app.json」

现象:导入项目后点编译,控制台红字提示找不到 app.json。原因:project.config.json 里的 miniprogramRoot 路径和实际目录不一致,或者你导入的层级不对。解决:打开 project.config.json,看 miniprogramRoot 的值,比如是miniprogram/,那你就得确保同级目录下确实有 miniprogram 文件夹,里面才有 app.json。如果源码把小程序代码放在src下,就把 miniprogramRoot 改成src/。还有一种情况是你导入时选错了目录,应该选到包含 project.config.json 的那一层,而不是它的父级。

5.2 真机预览白屏或接口请求失败

现象:开发者工具里正常,手机扫码预览白屏,或者数据加载不出来。原因:接口地址写的是 localhost 或 127.0.0.1,手机访问不到;或者后端没开外网访问;或者域名没备案、没配到小程序后台的 request 合法域名。解决:把 baseUrl 改成电脑的局域网 IP,手机和电脑连同一个 WiFi。如果还不行,检查电脑防火墙有没有拦 8080 端口。正式上线必须用 https 域名,且在小程序后台「开发管理」里配置 request 合法域名。毕业设计演示可以用「不校验合法域名」勾选,但真机预览时这个选项在手机端不生效,所以最好还是配个内网穿透或者用云开发。

5.3 购物车数量修改后总价不更新

现象:在购物车页点加号减号,数量变了,但底部总价没变。原因:setData 只更新了 cartList,没有重新计算 totalPrice,或者计算函数里用了错误的字段。解决:每次修改数量后,重新遍历 cartList,累加checked为 true 的price * count。注意价格单位,如果存的是分,总价也要用分算,最后展示再转元。另外,setData是异步的,如果你在修改后立刻读this.data.cartList,可能拿到旧值。正确做法是在setData的回调里计算,或者直接用修改后的数组算。

5.4 订单重复提交

现象:用户快速点两次「提交订单」,生成了两笔一样的订单。原因:按钮没有防抖,或者没有在提交时禁用按钮。解决:在 data 里加一个submitting标志,提交前设为 true,按钮加disabled="{{submitting}}",请求完成后再设回 false。后端也要做幂等,比如用订单号唯一索引,或者前端生成一个 requestId 传给后端,后端判断短时间内的重复请求直接返回已有订单。

5.5 图片外链失效导致首页空白

现象:首页轮播图和商品图都不显示,控制台报 403 或 404。原因:源码里的图片用的是第三方图床,时间久了被删或限流。解决:把图片下载到本地images目录,或者传到自己的对象存储。批量替换时用编辑器的全局搜索,把https://xxx.com/替换成/images/。如果图片太多,写个脚本批量下载:

#!/bin/bash # 从 sql 文件里提取图片 URL 并下载 grep -oE 'https?://[^"]+\.(jpg|png|jpeg)' flower_shop.sql | sort -u | while read url; do filename=$(basename "$url") curl -o "images/$filename" "$url" done

下载后把 SQL 里的 URL 也替换成本地路径。注意小程序包大小限制 2MB,图片多了要压缩,或者放到 CDN 上。

6. 从能跑到能答辩:改造思路与演示技巧

把源码跑通只是起点,毕业设计要的是「你的工作」。我一般会建议在花店基础上加两个差异化功能,比如「鲜花订阅」和「节日推荐」。订阅功能是用户选一个周期,比如每周一束,后端生成周期性订单。节日推荐是根据当前日期,首页置顶情人节、母亲节、教师节的花束。这两个功能不需要太复杂,但能让答辩老师看到你不是纯抄。

改造时优先动数据库,加两张表:subscription和festival。订阅表存用户 id、花束 id、周期、下次配送时间、状态。节日表存节日名称、日期、关联商品 id。小程序端加一个订阅页面,后台加一个节日管理页面。代码层面,订阅的定时任务可以用后端的@Scheduled注解,每天凌晨检查有没有到期的订阅,生成订单。

演示技巧方面,答辩前一定要录一个完整的操作视频,从登录、浏览、加购、下单、支付(模拟)、后台发货、用户确认收货,全流程走一遍。现场演示时用真机,提前把测试数据准备好,别现场录数据。如果老师问「你这个和网上源码有什么区别」,你就把订阅和节日推荐的功能打开,说清楚数据库改了哪几张表,前端加了哪几个页面,后端加了哪几个接口。这比空口说「我改了 UI」有说服力得多。

最后说个血泪经验:别等到答辩前一天才跑代码。我见过太多人,源码在电脑里躺了三个月,答辩前一周打开发现跑不起来,最后通宵改 bug。拿到源码第一周就把它跑通,然后花两周改造,留一周写论文和录视频。这样你才有后悔药可吃。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进