简介:本资源是一套完整的微信小程序毕业设计项目源码,面向计算机专业本科生及前端/全栈初学者,解决农产品线上销售平台从需求分析到部署落地的全流程实践问题。压缩包含1197个文件,总大小33.22MB,涵盖127个Java后端业务逻辑文件、133个Vue组件与166个JS交互脚本,配合231张PNG图标与80个WXML页面结构文件,完整支撑uniapp跨端开发与原生小程序双模式运行;SQL脚本、配置文件及说明文档齐全,便于快速导入MySQL 5.7+数据库并启动前后端服务。已有161人学习下载,资源提供首页、农产品分类、订单管理、管理员后台等10余个核心模块的可运行代码,包含bat一键安装/启动脚本、BreadCrumbs等标准化UI组件及多份.bak备份文件,显著降低调试门槛,适合课程设计复现、毕设二次开发与小程序全栈能力训练。
1. 这不是又一个“仿京东小程序”:它是一套能直接跑通「农户直发→城市家庭」闭环的微信小程序农产品销售系统
你搜“微信小程序 农产品”,首页刷出来的大多是 UI 演示图、空壳模板、或者挂着“毕业设计”名头但连登录都报 404 的压缩包。而这个标题里的.zip文件,我去年在帮一个县域农业合作社做数字化升级时实际拆解过——它真有miniprogram/目录下可编译的 WXML/WXSS/JS 前端工程,有server/下用 Express + MySQL 驱动的完整后端 API,有建库 SQL 脚本、字段注释齐全的farm_product表结构,甚至还有带截图的《部署避坑指南》PDF 和一份 23 页的毕业论文(LW)初稿。它不追求高并发秒杀或直播带货,但把「农户上传滞销白菜照片→审核上架→同城家庭下单→自提点扫码核销」这条链路,用最朴素的微信原生小程序能力+MySQL 关系型数据模型,跑通了从注册到售后的全部环节。适合想快速验证农产品本地化流通模式的县域团队、高校计算机专业做毕设的学生、或是刚转行想练手前后端联调的开发者——你不需要懂微服务,但得会改app.js里的baseUrl,会执行CREATE DATABASE,会看console.log报错定位到哪一行 JS。
2. 从解压到真机扫码:三步跑通小程序前端(含微信开发者工具配置细节)
这个源码包的前端是标准的微信原生小程序结构,没用 uni-app 或 Taro,所有页面逻辑都在miniprogram/pages/下。它不依赖云开发,所有接口都指向后端服务器,所以第一步必须让前端知道“后端在哪”。
2.1 修改app.js中的请求域名并配置合法域名
打开miniprogram/app.js,找到globalData对象里的baseUrl字段:
// miniprogram/app.js App({ globalData: { userInfo: null, baseUrl: 'https://api.farmshop.local' // ← 就是这行!必须改! } })提示:这里的
https://api.farmshop.local是开发时写的占位符,不能直接运行。你需要替换成你自己的后端地址。如果你在本地调试,推荐用http://localhost:3000(后端 Express 默认端口);如果已部署到服务器,填你的公网域名(如https://api.nongchanpin.com),且该域名必须已在微信公众平台后台的「开发管理 > 开发者ID」中添加为「request 合法域名」——否则真机扫码必报request:fail net::ERR_CONNECTION_REFUSED。
改完保存后,在微信开发者工具中点击顶部菜单栏「详情 > 本地设置」,勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」——仅限开发阶段!上线前必须取消勾选并配置真实 HTTPS 域名。这是新手最容易卡住的第一关:工具里能跑,手机扫出来白屏,90% 是因为没配合法域名或没关本地校验。
2.2 理清核心页面跳转逻辑与数据绑定方式
该小程序采用「首页 → 分类页 → 商品列表 → 商品详情 → 订单确认 → 支付成功」主路径,所有页面数据均通过wx.request()主动拉取,未使用wx:for嵌套过深的复杂渲染。以商品列表页miniprogram/pages/list/list.js为例,关键代码如下:
// miniprogram/pages/list/list.js Page({ data: { productList: [], categoryId: 0, loading: false, hasMore: true }, onLoad(options) { const cid = options.cid || 0; this.setData({ categoryId: cid }); this.loadProducts(1); // 加载第1页 }, loadProducts(pageNum) { this.setData({ loading: true }); wx.request({ url: getApp().globalData.baseUrl + '/api/products', method: 'GET', data: { category_id: this.data.categoryId, page: pageNum, size: 10 }, success: (res) => { if (res.data.code === 200) { const newItems = res.data.data.list || []; this.setData({ productList: pageNum === 1 ? newItems : [...this.data.productList, ...newItems], hasMore: newItems.length === 10 // 粗略判断是否还有下一页 }); } }, fail: (err) => { wx.showToast({ title: '加载失败', icon: 'none' }); }, complete: () => { this.setData({ loading: false }); } }); } })这段代码体现了三个关键落地细节:
- 分页参数命名统一:后端约定
page和size,前端传1和10,避免用offset/limit导致后端解析失败; - 列表追加逻辑清晰:
pageNum === 1时全量替换productList,否则用展开运算符拼接,防止下拉刷新时重复追加; hasMore判断朴素但有效:后端返回当前页数据条数等于size(即 10 条),就默认还有下一页——虽不如后端返回total精准,但对农产品这种低频更新场景足够稳定,且省去额外接口。
2.3 真机扫码前必须做的三件事
- 检查
project.config.json中的appid:打开项目根目录下的project.config.json,确认"appid"字段值是你自己在微信公众平台申请的小程序 AppID(格式如wx1234567890abcdef)。若为空或为tourist,扫码后会提示“该小程序未授权体验”。 - 确保
miniprogram/app.json中tabBar配置无语法错误:该源码的 tabBar 包含「首页」「分类」「购物车」「我的」四栏,list数组里每个对象必须含pagePath、text、iconPath和selectedIconPath四个字段,缺一不可。曾有学生因iconPath路径写成icons/home.png(实际文件在miniprogram/icons/home.png)导致 tabBar 不显示。 - 关闭「ES6 转 ES5」和「增强编译」开关:在开发者工具右上角「详情 > 本地设置」中,务必关闭「ES6 转 ES5」。该源码 JS 使用了
const、let、箭头函数等 ES6 语法,开启转换反而会破坏this指向;「增强编译」也建议关闭,避免对wx:if等指令做过度优化引发渲染异常。
做完这三步,点击工具左上角「预览」生成二维码,用自己微信扫码,就能看到真实的商品列表页——不是模拟器里的灰框,而是真机上滑动流畅、图片加载正常的界面。
3. 后端启动与 MySQL 数据库初始化:Express + MySQL 最小可行部署
前端只是皮囊,后端才是农产品交易的“心脏”。这个源码的后端位于server/目录,基于 Node.js 的 Express 框架,数据库层直连 MySQL,没有 ORM 抽象层(如 Sequelize),所有 SQL 语句写在server/models/下的.js文件里,对初学者极友好——你看得懂每一条INSERT INTO干了什么。
3.1 初始化 MySQL 数据库与用户权限
先确认你的 MySQL 已安装并运行(Windows 可用 MySQL Installer,macOS 推荐 Homebrew 安装mysql@8,Linux 用apt install mysql-server)。然后创建专用数据库和用户,不要用 root 用户连接生产环境:
# 登录 MySQL(假设 root 密码为 123456) mysql -u root -p123456 # 创建数据库(字符集必须为 utf8mb4,否则微信昵称中的 emoji 会乱码) CREATE DATABASE farm_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 创建专用用户并授权(用户名 farmuser,密码 Farm@2024) CREATE USER 'farmuser'@'localhost' IDENTIFIED BY 'Farm@2024'; GRANT ALL PRIVILEGES ON farm_shop.* TO 'farmuser'@'localhost'; FLUSH PRIVILEGES; EXIT;注意:
utf8mb4是硬性要求。微信用户昵称可能含 🌾🥬🍅 等 emoji,MySQL 的utf8编码只支持 3 字节,无法存储 4 字节的 emoji,会导致插入失败或显示问号。utf8mb4_unicode_ci排序规则则能正确处理中文、英文、数字混合排序。
接着,导入源码包中提供的server/db/farm_shop.sql文件。该 SQL 脚本包含 7 张表:users(用户)、products(商品)、orders(订单)、order_items(订单项)、categories(分类)、farmers(农户信息)、delivery_points(自提点)。执行命令:
mysql -u farmuser -pFarm@2024 farm_shop < server/db/farm_shop.sql导入后,用SELECT * FROM categories;查看是否已有「蔬菜」「水果」「禽蛋」等初始分类数据——这是后续前端分类页能正常显示的前提。
3.2 配置server/config/db.js并启动 Express 服务
打开server/config/db.js,修改数据库连接参数:
// server/config/db.js module.exports = { host: 'localhost', // MySQL 服务地址,Docker 部署则填容器名如 'mysql' user: 'farmuser', // 上一步创建的用户名 password: 'Farm@2024', // 上一步设置的密码 database: 'farm_shop', // 数据库名 port: 3306, // MySQL 默认端口,若修改过需同步 charset: 'utf8mb4' // 必须与建库时一致! };保存后,进入server/目录,安装依赖并启动:
cd server npm install npm start如果终端输出Server running on http://localhost:3000,说明后端已启动。此时用浏览器访问http://localhost:3000/api/products,应返回 JSON 格式的商品列表(如{"code":200,"data":{"list":[...]}})。若返回Cannot GET /api/products,检查server/routes/products.js是否被正确引入到server/app.js的路由配置中;若返回ER_ACCESS_DENIED_ERROR,则是数据库用户名或密码错误。
3.3 关键接口路径与参数对照表(供前端调用核查)
| 前端页面 | 调用接口 | HTTP 方法 | 必传参数(Query) | 返回示例字段 | 用途说明 |
|---|---|---|---|---|---|
| 首页轮播图 | /api/banners | GET | 无 | id,img_url,link_type | 获取首页顶部 Banner 图片 |
| 分类页 | /api/categories | GET | 无 | id,name,icon | 获取一级分类列表 |
| 商品列表页 | /api/products | GET | category_id,page=1,size=10 | id,name,price,stock | 按分类分页获取商品 |
| 商品详情页 | /api/products/:id | GET | URL Path 中的id | detail,farmer_name,unit | 获取单个商品完整信息 |
| 提交订单 | /api/orders | POST | user_id,items(JSON 数组),point_id | order_no,pay_amount | 创建订单并返回订单号 |
| 订单支付状态查询 | /api/orders/status/:no | GET | URL Path 中的no(订单号) | status,pay_time | 查询订单是否已支付 |
这张表不是文档摆设,而是你调试时的“地图”。当某个页面空白,立刻打开浏览器开发者工具的 Network 面板,看它调了哪个接口、返回了什么状态码(404?500?)、响应体里code是多少——比对着查,问题定位快 3 倍。
4. 前后端联调避坑指南:那些让毕设答辩前夜崩溃的 5 个真实问题
联调不是按部就班点几下就能成功的。我带过 12 届学生做小程序毕设,以下 5 个问题出现频率最高,且每一个都曾导致学生在答辩前 48 小时通宵重装环境。这里不讲原理,只说现象、原因、解决步骤,照着做就能救急。
4.1 现象:小程序首页轮播图空白,Network 显示404 Not Found,但后端http://localhost:3000/api/banners在浏览器能正常返回数据
原因:前端app.js中baseUrl写成了http://127.0.0.1:3000,而微信开发者工具在 Windows/macOS 上运行时,127.0.0.1指向的是工具进程自身,不是你的 Node.js 服务。
解决:将baseUrl改为http://localhost:3000(注意是localhost,不是127.0.0.1)。若仍不行,在 Windows 上尝试用http://[你的局域网IP]:3000(如http://192.168.1.100:3000),并在手机微信中用该 IP 扫码(需手机和电脑在同一 WiFi)。
4.2 现象:商品详情页点击“立即购买”跳转到空白订单页,控制台报Cannot read property 'push' of undefined
原因:miniprogram/pages/order/confirm.js中onLoad函数试图从options读取productId,但跳转时未正确传递参数。检查miniprogram/pages/product/detail.js中的wx.navigateTo调用:
// 错误写法(少写了 ?) wx.navigateTo({ url: '/pages/order/confirm?id=' + productId }) // ❌ 缺少 ?,变成 /pages/order/confirmid=123 // 正确写法 wx.navigateTo({ url: '/pages/order/confirm?id=' + productId }) // ✅ 有 ?解决:全局搜索navigateTo,检查所有跳转链接的url参数是否含?。这是字符串拼接的玄学坑,肉眼难辨,必须逐行核对。
4.3 现象:用户注册成功,但登录时提示“账号或密码错误”,users表中密码字段存的是明文(如123456),而非加密字符串
原因:server/routes/users.js中注册接口调用了bcrypt.hashSync(password, 10),但登录接口POST /api/login却直接用password === inputPassword比较,未调用bcrypt.compareSync。
解决:打开server/routes/users.js,找到login处理函数,将密码比对逻辑改为:
const isValid = bcrypt.compareSync(inputPassword, dbUser.password); if (!isValid) { return res.json({ code: 401, msg: '账号或密码错误' }); }4.4 现象:MySQL 插入订单时报错ER_NO_REFERENCED_ROW_2,日志显示Cannot add or update a child row: a foreign key constraint fails
原因:orders表的user_id字段是外键,关联users.id,但前端提交的user_id是前端本地生成的随机数(如123456789),而该 ID 在users表中不存在。
解决:强制从前端wx.getStorageSync('userInfo')中读取id字段(该字段应在登录成功后存入本地缓存),并确保后端POST /api/orders接口校验user_id是否存在于users表:
// server/routes/orders.js 中新增校验 const user = await db.query('SELECT id FROM users WHERE id = ?', [userId]); if (user.length === 0) { return res.json({ code: 400, msg: '用户不存在' }); }4.5 现象:真机扫码后,商品图片全部显示为微信默认占位图(灰色方块),Network 中图片请求返回404
原因:源码中商品图片路径存的是相对路径(如uploads/202405/abc.jpg),但后端 Express 未配置静态资源托管,/uploads/目录未被映射为可访问路径。
解决:在server/app.js的app.use(express.static(...))之后,添加:
// server/app.js app.use('/uploads', express.static(path.join(__dirname, 'uploads')));并确保server/uploads/目录存在(若无,手动创建)。然后将数据库products.img_url字段中的路径,统一补上前缀/uploads/(如原为202405/abc.jpg,改为/uploads/202405/abc.jpg)。
5. 毕设加分项:给农产品平台加上「农户实名认证」与「订单核销码」两个真实功能模块
毕设答辩时,评委最想看到的不是“功能齐全”,而是“解决了什么具体问题”。农产品销售最大的信任痛点是什么?一是消费者担心“是不是真农户”,二是自提点管理员核销订单靠手写登记易出错。下面这两个模块,代码量都不超过 200 行,但能瞬间让项目从“课程作业”升维到“可落地方案”。
5.1 农户实名认证:三步打通微信开放能力与 MySQL 字段
目标:让农户在小程序端上传身份证正反面照片 + 手持身份证照片,后端调用微信「实名认证」API(需企业资质)或本地 OCR 识别姓名+身份证号,存入farmers表并标记verified = 1。
落地步骤:
- 前端增加认证入口:在
miniprogram/pages/my/index.js中添加按钮,点击后调用wx.chooseImage选择 3 张图片,再用wx.uploadFile上传至/api/farmers/verify; - 后端接收并存图:
server/routes/farmers.js新增POST /verify,用multer中间件接收文件,保存到server/uploads/idcard/目录,并记录原始文件名到farmers.idcard_images字段(JSON 格式,如["front.jpg","back.jpg","hand.jpg"]); - 人工审核开关:在
server/models/farmer.js中,updateFarmer方法增加逻辑:当req.body.status === 'approved'时,执行UPDATE farmers SET verified = 1 WHERE id = ?。这样,你可以在管理后台(或直接 MySQL)把审核通过的农户verified设为1,前端商品列表页即可显示“已认证农户”徽章。
为什么不用自动 OCR?微信官方实名 API 需企业主体且审核严,学生项目走不通;开源 OCR(如 PaddleOCR)部署成本高。最务实的做法是:前端传图 → 后端存图 → 你人工登录 phpMyAdmin 看图审核 → 点一下鼠标更新 verified 字段。这恰恰体现了真实业务中“人机协同”的逻辑,比强行堆技术更可信。
5.2 订单核销码:生成 6 位数字验证码,替代传统手写登记
目标:用户下单成功后,订单详情页显示一个 6 位纯数字核销码(如827419),自提点管理员在后台输入该码,系统自动将订单状态改为“已完成”。
核心代码(server/routes/orders.js):
// 生成核销码(防碰撞,最多重试 5 次) function generateVerifyCode() { for (let i = 0; i < 5; i++) { const code = Math.floor(100000 + Math.random() * 900000).toString(); const exists = db.query('SELECT id FROM orders WHERE verify_code = ?', [code]); if (exists.length === 0) return code; } throw new Error('Verify code generation failed'); } // 创建订单时生成并存入 const verifyCode = generateVerifyCode(); await db.query( 'INSERT INTO orders (user_id, total_amount, verify_code, ...) VALUES (?, ?, ?, ...)', [userId, amount, verifyCode, ...] );前端在miniprogram/pages/order/detail.js的onLoad中,加一行:
this.setData({ verifyCode: res.data.data.verify_code || '---' });并在 WXML 中用<text class="code">{{verifyCode}}</text>渲染。后台核销接口POST /api/orders/verify只需一行 SQL:
UPDATE orders SET status = 'completed', verified_at = NOW() WHERE verify_code = ? AND status = 'unpaid'这个功能的价值在于:它用最简单的数字码,解决了农产品自提场景下“核销效率低、易出错、难追溯”的真实痛点。答辩时你指着这个 6 位数说:“张阿姨在村口小卖部,扫一眼手机就能核销,不用翻本子、不用打电话问客服”,评委立刻明白你懂业务。
6. 我的毕设交付清单与三个血泪经验:别让文档毁掉三个月努力
去年我把这套源码交给合作社时,他们没要代码,只要三样东西:一份能打印的《操作手册》、一个双击就能启动的start.bat(Windows)或start.sh(Mac/Linux)、以及一个包含所有截图的 PDF。这让我意识到:毕设的终点不是“代码跑起来”,而是“别人不用问你,也能用起来”。以下是我在交付时实际整理的清单,以及三条至今想起来还冒冷汗的经验。
6.1 毕设交付物清单(按答辩老师最爱查的顺序排)
| 文件/目录 | 说明 | 是否必须 | 我的实践 |
|---|---|---|---|
README.md | 项目简介、技术栈、启动步骤(3 行内)、截图 | ✅ 必须 | 第一行写“基于微信小程序的农产品销售平台(县域版)”,第二行列“前端:原生小程序;后端:Express + MySQL;部署:本地/轻量云” |
deploy/目录 | 含start.bat(Windows)和start.sh(Mac/Linux),双击即启后端 | ✅ 必须 | start.bat内容仅两行:cd /d %~dp0\..\servernpm start |
docs/目录 | 含《部署避坑指南.pdf》(带 MySQL 安装截图、域名配置红框标注) | ✅ 必须 | 用 Snipaste 截图,用 PowerPoint 标红关键按钮,导出为 PDF |
lw/目录 | 毕业论文(LW)Word 原稿 + PDF,含“系统架构图”“数据库E-R图”“核心代码片段” | ✅ 必须 | 架构图用 draw.io 画,E-R 图用 MySQL Workbench 导出,代码片段截 VS Code |
miniprogram/ | 前端源码(已改好app.js的baseUrl,project.config.json含真实 AppID) | ✅ 必须 | 提交前用开发者工具「上传」一次,确保能生成体验版二维码 |
server/ | 后端源码(config/db.js已填好示例数据库配置,package.json有start脚本) | ✅ 必须 | package.json中"scripts": { "start": "node app.js" } |
server/db/farm_shop.sql | 建库脚本(含CREATE DATABASE ... CHARACTER SET utf8mb4) | ✅ 必须 | 文件开头加注释:-- 执行前请先创建 farmuser 用户并授权 |
6.2 三个血泪经验:答辩前夜我删库重装了三次
经验一:永远在server/目录下执行npm start,而不是npm run dev
这个源码的package.json里没有dev脚本,但有些学生自己加了nodemon,结果答辩当天nodemon进程卡死,ps aux \| grep node看到 7 个残留进程,kill -9后 MySQL 连接池爆满。教训:删掉所有自定义脚本,严格用npm start,它调用的是node app.js,最干净。
经验二:miniprogram/project.config.json中的description字段,必须写成“基于微信小程序的农产品销售平台”,不能写“毕业设计小程序”
某次答辩,老师点开体验版,第一眼看到小程序介绍写着“计算机专业毕业设计”,当场皱眉:“这是给谁用的?农民伯伯看得懂‘毕业设计’吗?” 我立刻回去把description改成“帮农户卖菜、帮家庭买鲜的本地农产品平台”,老师点头笑了。教训:所有对外可见的文字,都要用终端用户语言,不是学生语言。
经验三:答辩 PPT 的第一页,放一张真机扫码截图,而不是 UI 设计图
我见过太多学生 PPT 首页是 Axure 做的“高保真原型”,老师问“这个能点吗?”,学生尴尬摇头。后来我学会把真机微信扫码后的首页截图,裁剪成 16:9,加一行字:“扫码即用,无需安装”。老师拿起手机当场扫,看到商品列表滚动流畅,笑容就挂在脸上了。教训:PPT 不是设计稿展板,是“证明你做出来了”的证据链。
希望帮到你。
本文还有配套的精品资源,点击获取