简介:这是一套面向计算机相关专业学生与项目实战学习者的微信小程序农产品自主供销系统,可作为毕业设计、课程设计或期末大作业的完整参考方案。资源包共1399个文件,约14.69MB,涵盖前端小程序页面(wxml、wxss、js、json)、后端Java源码、Vue管理端组件、SQL数据库脚本以及png、svg、jpg等界面素材与图标资源,并附带bat启动脚本、配置文件与说明文档,目录结构清晰,便于按模块查阅与二次开发。系统围绕农产品供销场景,包含商品展示、订单管理、用户权限等典型业务模块,适合需要快速搭建小程序全栈项目、理解前后端交互流程的学习者。目前已有123人学习下载,可帮助读者掌握小程序页面布局、接口调用与数据库设计思路,并对照源码完成功能调试与论文撰写,节省从零搭建项目的时间成本。
1. 农产品自主供销小程序:从田间到餐桌的订单闭环怎么搭
农产品自主供销小程序,解决的是农户、合作社与消费者之间信息不对称、中间环节多、订单散乱的问题。它和普通电商小程序最大的区别在于:商品是季节性、非标品,库存随采摘波动,下单后往往需要人工确认再发货。源码和数据库是这类项目的核心资产——源码决定业务逻辑能不能改,数据库决定订单、库存、用户数据怎么存、怎么查、怎么同步。适合想接农业数字化项目的开发者、需要给合作社做自建渠道的运营者,以及拿它当微信小程序全栈练手项目的学生。常见做法是前端用微信小程序原生或 uniapp,后端用 Node.js 或 Java,数据库用 MySQL 或 SQLite。下面按落地顺序拆开讲。
2. 技术选型与数据库设计:先定骨架再写页面
2.1 前端选原生还是 uniapp
农产品供销小程序的功能不复杂:商品列表、详情、购物车、下单、订单查询、农户入驻。原生微信小程序开发工具链成熟,调试方便,适合只发微信端的场景。uniapp 的优势是一套代码可以同时出微信小程序和 App,如果合作社后续想做个安卓端给收购商用的,选 uniapp 更省事。我一般会先问一句:只发微信还是多端都要?只发微信就原生,多端就 uniapp。
原生小程序的目录结构很固定,pages放页面,utils放请求封装,components放复用组件。农产品项目里商品卡片、订单状态标签、数量选择器这三个组件复用率最高,建议一开始就抽出来。
// utils/request.js 请求封装,统一处理 baseURL 和 token const BASE_URL = 'https://your-domain.com/api'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200) { resolve(res.data); } else if (res.statusCode === 401) { // token 过期,跳转登录 wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { reject(res.data); } }, fail(err) { reject(err); } }); }); } module.exports = { request };这段封装做了三件事:拼 baseURL、带 token、统一处理 401。参数说明:BASE_URL换成你自己的后端地址;Authorization头里的 token 从本地缓存取,登录成功后写入。注意微信小程序要求所有请求域名在后台配置白名单,本地调试可以在开发者工具里勾选“不校验合法域名”。
2.2 数据库表怎么设计才扛得住订单
农产品订单有个特点:同一批货可能来自多个农户,下单后需要合并发货或分单。数据库设计要提前考虑这个。核心表至少五张:用户表、商品表、订单表、订单明细表、农户表。
-- 商品表:农产品是非标品,规格用 JSON 存 CREATE TABLE `product` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `farmer_id` INT UNSIGNED NOT NULL COMMENT '所属农户', `name` VARCHAR(100) NOT NULL COMMENT '品名', `cover` VARCHAR(255) DEFAULT '' COMMENT '封面图', `price` DECIMAL(10,2) NOT NULL COMMENT '单价', `unit` VARCHAR(20) DEFAULT '斤' COMMENT '单位', `stock` INT NOT NULL DEFAULT 0 COMMENT '库存', `spec` JSON DEFAULT NULL COMMENT '规格,如{"重量":"5斤","产地":"山东"}', `status` TINYINT DEFAULT 1 COMMENT '1上架 0下架', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_farmer` (`farmer_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表:状态机是关键 CREATE TABLE `order` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单号', `user_id` INT UNSIGNED NOT NULL, `total_amount` DECIMAL(10,2) NOT NULL, `status` TINYINT DEFAULT 0 COMMENT '0待确认 1已确认 2已发货 3已完成 4已取消', `address` VARCHAR(255) NOT NULL, `remark` VARCHAR(255) DEFAULT '', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:spec用 JSON 类型存非标规格,比开一堆字段灵活;status用 TINYINT 做状态机,0 到 4 分别对应待确认、已确认、已发货、已完成、已取消。注意订单号order_no要加唯一索引,防止重复提交。库存字段stock在高并发下要用UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?这种带条件的更新,避免超卖。
2.3 订单状态流转与库存扣减
农产品订单不能像普通电商那样下单即扣库存,因为农户可能同时在线下卖货。常见做法是下单时冻结库存,确认后再实扣。简单点也可以下单直接扣,取消时回滚。关键是状态流转要写清楚,不然后面查订单全是玄学。
// 下单接口核心逻辑(Node.js + MySQL 示例) async function createOrder(userId, items, address) { const conn = await pool.getConnection(); try { await conn.beginTransaction(); let totalAmount = 0; const orderNo = 'AG' + Date.now() + Math.floor(Math.random() * 1000); // 1. 校验库存并扣减 for (const item of items) { const [result] = await conn.execute( 'UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?', [item.count, item.productId, item.count] ); if (result.affectedRows === 0) { throw new Error(`商品 ${item.productId} 库存不足`); } totalAmount += item.price * item.count; } // 2. 写订单主表 const [orderResult] = await conn.execute( 'INSERT INTO `order` (order_no, user_id, total_amount, address, status) VALUES (?, ?, ?, ?, 0)', [orderNo, userId, totalAmount, address] ); // 3. 写订单明细 for (const item of items) { await conn.execute( 'INSERT INTO order_item (order_id, product_id, count, price) VALUES (?, ?, ?, ?)', [orderResult.insertId, item.productId, item.count, item.price] ); } await conn.commit(); return { orderNo, orderId: orderResult.insertId }; } catch (err) { await conn.rollback(); throw err; } finally { conn.release(); } }逻辑说明:整个下单过程放在一个事务里,库存扣减用带条件的 UPDATE,只有stock >= count时才扣成功,否则抛错回滚。参数说明:items是前端传来的商品数组,每项包含productId、count、price;address是收货地址字符串。注意价格要在后端重新查一遍,不能信前端传的price,否则会被改价。
3. 小程序端核心页面与接口联调
3.1 商品列表与详情页的数据绑定
商品列表页要处理分页加载和下拉刷新。农产品图片多,建议列表只传缩略图,详情页再加载大图。微信小程序的onReachBottom和onPullDownRefresh是两个关键生命周期。
// pages/goods/list.js const { request } = require('../../utils/request'); Page({ data: { list: [], page: 1, pageSize: 10, hasMore: true, loading: false }, onLoad() { this.loadList(); }, onPullDownRefresh() { this.setData({ page: 1, list: [], hasMore: true }); this.loadList().then(() => wx.stopPullDownRefresh()); }, onReachBottom() { if (this.data.hasMore && !this.data.loading) { this.loadList(); } }, async loadList() { if (this.data.loading) return; this.setData({ loading: true }); try { const res = await request({ url: '/product/list', data: { page: this.data.page, pageSize: this.data.pageSize } }); const newList = this.data.list.concat(res.data.list); this.setData({ list: newList, page: this.data.page + 1, hasMore: res.data.list.length === this.data.pageSize, loading: false }); } catch (err) { this.setData({ loading: false }); wx.showToast({ title: '加载失败', icon: 'none' }); } } });参数说明:page从 1 开始,每次加载后加 1;hasMore根据返回条数是否等于pageSize判断。注意onReachBottom里要加loading锁,否则快速滑动会重复请求。下拉刷新时重置page和list,并调用wx.stopPullDownRefresh()停止动画。
3.2 购物车与下单页的本地存储
购物车数据建议存在本地缓存,下单时再提交到后端。微信小程序的wx.setStorageSync同步存储适合购物车这种小数据量场景。
// 加入购物车 function addToCart(product, count) { let cart = wx.getStorageSync('cart') || []; const index = cart.findIndex(item => item.productId === product.id); if (index > -1) { cart[index].count += count; } else { cart.push({ productId: product.id, name: product.name, price: product.price, cover: product.cover, count: count }); } wx.setStorageSync('cart', cart); wx.showToast({ title: '已加入购物车', icon: 'success' }); } // 下单页读取购物车 Page({ data: { cart: [], total: 0 }, onShow() { const cart = wx.getStorageSync('cart') || []; const total = cart.reduce((sum, item) => sum + item.price * item.count, 0); this.setData({ cart, total: total.toFixed(2) }); } });逻辑说明:购物车用productId去重,已存在就加数量。参数说明:cart数组每项包含商品基本信息和数量;total用reduce累加,保留两位小数。注意下单成功后要清空购物车wx.removeStorageSync('cart'),否则会重复下单。
3.3 订单列表与状态筛选
订单列表要按状态筛选,微信小程序的picker或自定义 tab 都可以。接口传status参数,后端按状态查。
// pages/order/list.js Page({ data: { tabs: ['全部', '待确认', '已确认', '已发货', '已完成'], activeTab: 0, orders: [] }, onTabChange(e) { const index = e.currentTarget.dataset.index; this.setData({ activeTab: index }); this.loadOrders(index === 0 ? null : index - 1); }, async loadOrders(status) { const res = await request({ url: '/order/list', data: { status: status } }); this.setData({ orders: res.data.list }); } });参数说明:status为null时查全部,否则按 0 到 3 筛选。注意后端要校验user_id,只能查自己的订单,不能查别人的。
4. 避坑与排查:农产品小程序最容易翻车的 5 个点
4.1 库存超卖:并发下单时 stock 扣成负数
现象:两个用户同时下单同一商品,库存只剩 1,结果两个人都下单成功,库存变成 -1。原因:先查库存再更新,中间有时间差。解决:用UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?,根据affectedRows判断是否扣成功,失败就回滚事务。
4.2 订单号重复:高并发下 Date.now() 不够用
现象:同一毫秒内多个订单生成相同订单号,插入报唯一索引冲突。原因:Date.now()毫秒级精度不够。解决:订单号加随机数或用户 ID 后缀,或者用数据库自增 ID 拼接。更稳的做法是用 Redis 的INCR生成序列号。
4.3 图片加载慢:农产品大图没压缩
现象:商品列表页图片加载慢,用户滑半天出不来。原因:农户上传的原图几 MB 一张,直接返回给小程序。解决:后端上传时用sharp或jimp压缩,生成缩略图和大图两个版本,列表用缩略图,详情用大图。微信小程序本身也有image组件的lazy-load属性,记得打开。
4.4 数据库连接池耗尽:请求多了就卡死
现象:小程序用一会儿就报 500,后端日志显示Too many connections。原因:每次请求都新建数据库连接,没释放。解决:用连接池,Node.js 里mysql2的createPool,Java 里用 HikariCP。连接数根据并发量设,一般 10 到 20 够用。注意conn.release()要放在finally里,确保异常时也释放。
4.5 微信登录态过期:token 失效没跳转
现象:用户用了一段时间后,下单提示未登录,但页面没跳登录页。原因:token 过期后接口返回 401,前端没统一处理。解决:在request封装里判断 401,清除本地 token 并跳转登录页。注意登录页要能回跳到原页面,用wx.navigateTo的url带参数,或者用getCurrentPages拿当前路径。
5. 源码二次开发与数据库同步的进阶技巧
拿到源码后,第一件事不是改页面,而是把数据库跑起来。常见做法是导入sql文件,改config里的数据库连接信息,然后跑一遍下单流程,看哪张表报错。农产品项目的数据库同步,如果只是本地开发和线上环境,用mysqldump导出导入就行。命令是mysqldump -u root -p dbname > backup.sql,恢复用mysql -u root -p dbname < backup.sql。注意字符集要统一utf8mb4,否则农户名字里的生僻字会乱码。
二次开发时,我一般会先加一个admin后台,用来管理农户和商品。后台不用太复杂,能用就行。接口复用小程序端的,加个role字段判断权限。数据库层面,给farmer表加个status字段,审核通过才能上架商品。
验证方法:本地跑通后,用微信开发者工具的“真机调试”扫一遍,重点看下单、支付、订单查询三个流程。支付需要商户号,没有的话可以先模拟,把wx.requestPayment换成wx.showToast提示成功,先把业务逻辑跑通。
最后说个血泪经验:农产品小程序最怕的是农户不会用。后台要做得极简,最好一个页面搞定上架和改价。数据库里product表的spec字段虽然灵活,但农户填不明白,不如直接开几个固定字段:重量、产地、保质期。源码是死的,业务是活的,别为了技术优雅牺牲可用性。希望帮到你。
本文还有配套的精品资源,点击获取