ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

农业收成销售管理系统开发:Python+Vue3全栈实践

农业收成销售管理系统开发:Python+Vue3全栈实践 接到这个“农业农产品收成销售管理系统”需求的时候客户给的描述很朴素把全村合作社每年的收成记下来卖出去的时候也有个台账年底能算清楚到底赚了多少。听起来就是个进销存但真正动手拆需求才发现农业业务里那些“不那么标准”的环节恰恰是系统设计里最费心思的地方。最终我采用 Python 做后端、Vue3 做前端从收成登记到销售出库再到可视化报表完整跑通了一套前后端分离的系统。这篇就把我整个项目的需求拆解、技术选型、核心设计和踩坑记录都整理出来给同样要做类似管理系统的朋友一个可抄的作业。1. 项目概述与核心思路1.1 这个系统到底要解决什么业务问题农业收成和销售管理本质上是三个过程种下去、收上来、卖出去。但实际业务里每个环节都不像 ERP 里那么规整。比如“收成”这件事不是一个仓库入库单能表达的——同一块地可能套种了玉米和豆角一批货可能分三天摘完摘下来的品相不同价格也不同到了销售环节又有一级批发商、电商渠道、单位团购等不同客群价格随行就市回款周期还不一样。所以我拿到需求后没有急着建表而是把业务流程捋了一遍梳理出几个关键节点基地/地块档案记录种植区域、面积、当前种植作物和预计成熟时间。收成登记按批次记录采收日期、作物、地块来源、产量斤/吨、品相等级、存放位置。库存管理收成入库存以后销售订单从库存里扣减支持按批次先进先出。销售管理记录客户信息、销售订单、单价、金额、回款状态。统计分析按月度、季度、年度查看收成量、销售额、客户排行、作物结构。这套系统适合的群体也很明确合作社管理员、家庭农场主、农业公司运营人员还有负责记账的财务。它要解决的不是复杂的农事管理比如施肥打药而是“收了多少、放在哪、卖了多少钱、钱回来没有”这个闭环。1.2 为什么选 Python 后端搭配 Vue3 前端选型阶段我其实纠结过是否用 Node 或者 Java但最终还是定了 Python 后端加 Vue3 前端的组合。原因很实际。Python 侧我的考虑是业务系统里除了常规的 CRUD还有一块很重的统计分析需求比如按作物分组统计收成量、按月份统计销售趋势、计算商品损耗率。Python 在这类数据聚合场景下写起来非常顺手尤其配合 Pandas 做二次加工也好配合 SQLAlchemy 做 ORM 聚合查询也好代码量都比 Java 少一个量级。FastAPI 作为 API 层自带 OpenAPI 文档联调时前端直接拿接口文档对着调省掉了写接口说明文档的功夫。Vue3 选型则更简单这套系统要兼容平板和电脑访问要拆分成组件来维护Vue3 的组合式 API 在处理这类中后台业务时比 Vue2 的选项式 API 更容易组织逻辑。加上 Element Plus 组件库本身就为 Vue3 做了适配表格、表单、弹窗这些管理系统常见元素开箱即用开发速度能拉满。而且 Vue3 用 Vite 做构建冷启动速度比 Webpack 快数倍对于页面多、组件多的后台项目体验提升非常明显。整体架构就是经典的“前后端分离”Vue3 做单页应用通过 Axios 调用 RESTful 接口Python 后端FastAPI提供数据接口和业务逻辑ORM 层访问 MySQL 数据库文件存储和图片上传走本地磁盘 静态映射后续部署用 Nginx 托管前端静态文件并反向代理接口。1.3 整个项目我拆成了哪几个功能模块系统按角色和使用场景拆成了六个核心模块模块主要功能服务对象地基地块管理地块信息、种植计划、作物轮作记录管理员、种植组长收成批次管理采收登记、批次入库、质检等级、损耗报损库管员库存管理批次库存明细、库存盘点、存量预警库管员、管理员销售订单管理客户管理、订单创建、出库扣减、回款登记销售员、财务统计报表中心收成趋势、销售排行、利润汇总、月度台账管理员、老板系统管理用户管理、角色权限、操作日志系统管理员每个模块都围绕“批次”这一条主线串起来。一个收成批次从地头采收开始就有了唯一编号入库存、出库销售、损耗报损都挂在批次号下面。这样做的好处是任何一个卖出去的订单都能反查到它的产地、采收日期和原始等级出了问题可以精准回溯到源头批次。这是我在设计阶段最坚持的一个点也是后面实际使用中客户评价最高的一块。2. 数据库设计与关键表结构2.1 一张表把收成批次讲清楚收成登记是整条业务链的源头所以我在设计表结构时花了很大精力。收成批次表我定义为harvest_batch核心字段如下字段名类型说明batch_novarchar(32)批次号按日期地块编码生成land_idint关联地块crop_idint关联作物品种harvest_datedate采收日期quantitydecimal(10,2)采收数量单位默认保留到斤unitvarchar(10)单位斤/吨/箱gradevarchar(10)品相等级一级/二级/等外storage_locationvarchar(50)存放库位statustinyint状态待入库/已入库/已出库/部分出库/已报损operator_idint登记人remarkvarchar(255)备注这里面有个细节quantity虽然字段是入库总数量但“可用库存量”不能只靠quantity减掉订单数量算出来。因为农产品存在自然损耗和挑拣损耗我加了一张batch_stock_log流水表每次出入库都记录操作类型和数量变动值库存表的当前可用量 初始入库量 - Σ(出库量) - Σ(报损量)。这种流水账方式虽然多了一张表但胜在任何一笔变动都有据可查。2.2 销售订单怎么和库存批次联动销售订单表sales_order记录订单头信息订单号、客户ID、销售员ID、订单总金额、已回款金额、状态待出库/已完成/已取消、下单时间。订单明细表sales_order_item记录每一行卖的是什么作物、数量、单价、关联的批次ID。为什么订单明细要关联批次因为农产品定价会随批次波动同一个客户同一笔订单里可能同一作物在不同批次价格都不同。如果不关联批次售价就只能记一个平均价后续财务核算毛利时会出现偏差。我做的做法是前端创建订单时选择作物系统自动带出该作物当前所有有可用库存的批次列表列表里显示剩余量、等级、价格销售员直接在批次行填写销量系统校验“本次销量不能超过该批次可用库存”。这一套逻辑写下来逻辑闭环了但性能上有个小坑需要注意如果某个批次库存量巨大前端一次性拉全部批次会卡。我的方案是后端提供一个“可售库存查询接口”参数是作物ID接口内部只查quantity - 已出库 - 报损 0的批次默认最多返回 50 条并按入库时间倒序排列保证先入库的先卖。2.3 权限设计直接放到业务层没有用复杂到需要用 RBAC 全家桶的权限框架我把角色做成一张独立表用户关联角色角色再关联权限码。权限码是数组字段前端在登录后拿到一份权限码列表按字段级控制按钮显隐后端的每个接口也加上了权限码校验。之所以放业务层而不是依赖框架是因为农业公司的人员结构经常变动——农忙时临时工要录收成农闲时又只留核心人员——权限调整的频次很高。直接在业务层写一个check_permission依赖每次请求进来先解析 JWT再从 Redis 里取用户权限码集合做判断灵活且好维护。后端权限校验代码大概是这个样子def require_permission(code: str): def decorator(authorization: str Header(...)): user jwt_decode(authorization) perms get_user_permissions(user[id]) if code not in perms: raise HTTPException(status_code403, detail无操作权限) return user return decorator3. 后端核心实现FastAPI 接口设计与业务逻辑3.1 项目目录结构和启动流程后端我按模块划分目录不是传统的 models/routers/schemas 三级包而是按业务垂直切割app/ core/ # 配置、数据库会话、JWT工具、公共依赖 modules/ land/ # 地基地块模块 harvest/ # 收成批次模块 stock/ # 库存流水模块 sale/ # 销售订单模块 stats/ # 统计报表模块 system/ # 用户、角色、日志模块 utils/ # 通用工具如Excel导出、日期处理每个模块内部自包含router.py、models.py、schemas.py、service.py四个文件。这么分层的用意是业务系统迭代时改动通常集中在某一个业务域里。比如销售流程改了只需要动sale目录下的文件不用在全局 models 里翻来翻去。数据库初始化用 SQLAlchemy 的create_all不够用我直接写了一个init_db.py脚本里面先建库再建表最后灌入默认角色和管理员账号。启动上我用的 Uvicorn 多进程模式生产环境一般起 4 个 workeruvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 43.2 收成登记接口的参数设计收成登记的核心接口我设计为POST /api/v1/harvest/batches请求体大致如下{ land_id: 3, crop_id: 7, harvest_date: 2024-06-18, quantity: 1250.0, unit: 斤, grade: 一级, storage_location: 1号冷库A区, source_batch_no: , remark: 早茬玉米第二批次采收 }后端 service 处理时有几个点容易忽略但必须要做地块和作物的合法性校验地块 ID 必须存在且状态为“种植中”。批次号的生成YC 日期 地块编码 序号保证同一地块同一天可以有多批次。默认把批次状态置为pending待入库同时插入一条库存流水记录【入库】批次xxx首录。如果 quantity 后小数超过 2 位全部四舍五入保留 2 位避免后续单价×数量对不上。这个接口用事务包裹批次主表和库存流水表同时提交。这样做的目的很明确即使数据库在第二步写入时报错批次数据也不会变成孤岛数据。3.3 库存扣减的原子性与并发安全农产品销售有个显著特点高峰期集中出库比如蔬菜成熟那几天一天可能有几十个订单同时从同一个批次里扣库存。如果库存扣减不做并发控制很容易出现“超卖”——两个订单都扣成功了实际库存只剩一份。我的处理方式是数据库行级锁 事务重试async def deduct_stock(order_item): async with db.begin(): stock await db.execute( select(BatchStock).where(BatchStock.batch_id order_item.batch_id).with_for_update() ) if stock.available_quantity order_item.quantity: raise BusinessError(批次库存不足) stock.available_quantity - order_item.quantitywith_for_update()会把该批次库存行锁住同一时间只有一个请求能够进入修改。在线事务里即使后面又加锁检查也一定要拿到当前最新库存值不要用之前缓存的数量。为了让用户遇到并发时体验不那么生硬我还在接口层做了异常捕获如果捕获到数据库锁等待超时返回“系统繁忙请重试”并自动重试最多 3 次。实际操作中合作社那种低并发的场景几乎碰不到这个问题但做了总比不做稳妥。3.4 销售回款与财务报表联动销售订单完成出库以后实际还差一个关键动作——回款记录。农业赊销很常见客户把货拉走了钱可能一个月后才打过来。销售订单表里我做了total_amount和paid_amount两个字段每登记一笔回款就更新paid_amount并在回款流水表记录。财务报表的统计口径要分两头销售额 订单总金额不管回没回款。实际回款 回款流水表求和。如果混在一起算老板看到账面上赚了很多但是现金没回来会误判经营状况。我在统计接口里单独给了两个维度前端页面也分开展示销售业绩看订单金额回款看收款流水。这种细节非常影响真实使用体验我强烈建议做农业管理系统的人不要忽略。4. 前端核心实现Vue3 Element Plus 实战记录4.1 组合式 API 在项目里的组织方式Vue3 里组合式 API 提供了比 Vue2 更灵活的逻辑复用方式。我在项目里基本所有页面都用了script setup并且把业务逻辑封装成一个个 composable可组合函数。比如收成批次列表页面我把“批次筛选条件、分页参数、列表数据加载、批次入库操作”封装成一个useBatchList的函数export function useBatchList() { const loading ref(false) const filters reactive({ landId: null, cropId: null, status: }) const page reactive({ current: 1, size: 10, total: 0 }) const list ref([]) const loadData async () { loading.value true try { const res await api.get(/harvest/batches, { params: { ...filters, ...page } }) list.value res.data.records page.total res.data.total } finally { loading.value false } } return { loading, filters, page, list, loadData } }页面组件里只负责调用这些函数和渲染视图多个页面也能复用一个 composable。以前 Vue2 时代靠 mixin 混入逻辑变量来源不清查 bug 像破案一样现在组合式 API 让数据流一眼看得清楚我强烈推荐新人直接熟悉这种写法不要再用旧习惯写了。4.2 动态表单收成上报页面的细节处理收成上报页虽然只是一个“加一条记录”的需求但真正做起来需要处理好几个交互细节。首先采集日期默认当天地块选择后自动带出该地块正在种植的作物省去再选一次作物的步骤其次数量输入要有单位切换选了“吨”后保存前自动转换成“斤”进行存储避免口径混乱。还有一个小但很关键的体验点表单里“品相等级”的下拉选项要和当前登录用户的权限联动。普通库管员只能选“待检”质检员才能选“一级/二级/等外品”。这个用 Vue3 的 computed 属性根据用户权限码动态过滤选项就行。Form 表单走 Element Plus 的校验规则日期用date-picker数量用input-number限制最小为 0.01。校验规则单独拎出来新手可以照着写const rules { landId: [{ required: true, message: 请选择地块, trigger: change }], cropId: [{ required: true, message: 请选择作物, trigger: change }], harvestDate: [{ required: true, message: 请选择采收日期, trigger: change }], quantity: [{ required: true, message: 请输入数量, trigger: blur }] }一个细节数量字段我用的是blur触发校验而不是change因为 input-number 组件在点击加减按钮时也会触发 change用户数字还没输完就报错体验会被打断。4.3 ECharts 可视化销售趋势和收成对比统计报表页是老板用得最多的页面我用 ECharts 做了三个核心图表月度销售趋势折线图、各作物收成占比饼图、客户销售额排行条形图。ECharts 在 Vue3 里的使用很轻量不需要额外封装库直接安装echarts在组件里onMounted时初始化图表实例onUnmounted时销毁即可。但要注意一点如果图表依赖异步接口数据渲染顺序不能乱。我的做法是先用接口加载数据数据返回后再初始化图表而不是先初始化后setOption——后者经常会出现宽度計算为 0 导致图表挤压的问题。月度销售趋势的统计接口后端返回的是[{ month: 2024-06, amount: 28000 }]这种结构前端直接映射为 X 轴和 Y 轴两数组。做的时候我加了一层格式化金额保留到万元不足一万就原样显示这符合农业公司老板的看数习惯。4.4 前后端联调阶段的接口规范问题联调是最容易出现扯皮的时候我在项目里直接用 FastAPI 自动生成的 OpenAPI 文档默认/docs路径作为接口契约。前端请求路径、请求方法、参数类型全部以文档为准后端代码有更新文档也跟着更新省了手写接口文档的时间。另外几个联调时的硬规则我统一写成文档发给团队所有列表接口统一返回分页结构{ records: [], total: 0 }。日期时间字段统一为YYYY-MM-DD或YYYY-MM-DD HH:mm:ss禁止时间戳。金额字段后端返回字符串前端展示时统一做千分位格式化。请求错误统一走拦截器弹出ElMessage.error业务错误码code ! 0时不打印堆栈。前端 Axios 请求封装里做一个响应拦截器遇到 401 时自动跳登录页并清理用户缓存。这个不做的话系统用时间长了 token 过期页面一直报错而用户不明所以体验极差。5. 部署上线与数据库运维5.1 Nginx 托管前端加反向代理的部署方案部署时我没有用 Docker 那一套农业公司的服务器环境通常比较单一直接装 Nginx 和 Python 虚拟环境最省事。前端npm run build构建后生成dist目录放到服务器/var/www/agri_system/下后端通过 Uvicorn 启动在127.0.0.1:8000Nginx 配置做两层映射server { listen 80; server_name your_domain_or_ip; root /var/www/agri_system; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /upload/ { alias /var/www/agri_uploads/; } }location /里的try_files是单页应用的核心前端路由切换时刷新页面如果找不到对应路径就重写到index.html交给 Vue Router 处理。没有这一步用户一刷新页面就是 404。5.2 数据库备份策略农业数据虽然不像金融数据每分每秒都金贵但收成记录和销售台账是整年下来的经营基础丢了一年的数据等于全部白干。我的备份策略很简单MySQL 每天凌晨 3 点用mysqldump全量备份一次保留最近 30 天的备份文件每周日再额外导出一份归档到另一个磁盘目录。备份命令写成定时任务0 3 * * * mysqldump -u agri_user -p密码 agri_db /backup/agri_$(date \%Y\%m\%d).sql find /backup -name *.sql -mtime 30 -delete恢复备份前一定要先看备份文件大小是否正常。我踩过一次坑定时任务因为磁盘满了静默失败备份文件是 0 字节恢复时才发现完全没数据。现在我的备份脚本里加入了“文件大小小于 100KB 则报警”的判断防止裸奔。5.3 移动端适配与网络不稳定的应对农业场景里使用电脑的场合其实不多绝大多数情况下员工是在田间用手机或者平板录入数据。所以前端我用了响应式布局表格页面在窄屏下自动隐藏次要列表单页面采取单列布局。这里建议不要重新适配一套移动端时间和精力都不划算。更关键的是断网和弱网处理。地头信号不好是常态提交请求超时后如果用户重新点提交很可能出现重复入库。我在前端做了两个处理全局 Axios 取消重复请求机制相同请求还在 pending 中时后发起的同类请求直接取消。业务接口做了幂等性处理收成批次号唯一索引同一个批次号第二次提交会被拒绝。如果后续想做得更完善可以在移动端加入本地离线草稿箱把待提交数据先存到浏览器 localStorage等网络恢复后统一提交。这是农业管理系统的一个加分项但目前优先级不高。6. 常见问题排查与避坑技巧6.1 高频问题速查表我把实际开发过程中遇到的典型问题整理成表方便各位直接对照处理现象根本原因解决办法前端登录后刷新页面就白屏Vue Router history 模式未配置 Nginx fallback加try_files $uri $uri/ /index.html;收成数量是 1000.1 斤出库时显示 1000 斤数据库字段类型用了decimal(10,0)改成decimal(10,2)并重建表结构库存扣减并发导致超卖读取和更新之间没有加行锁使用with_for_update()包裹查询前端表格日期显示为2024-06-18T00:00:00FastAPI 返回 ISO 格式字符串前端统一dayjs格式化处理上传图片大小超过 1MB 被拒Nginx 默认client_max_body_size 1m配置client_max_body_size 20m;两个库管员同时录入同一批次重复数据缺乏幂等控制批次号加唯一索引提交前先查询图表在弹窗里宽度为 0ECharts 在元素隐藏时初始化无法计算尺寸弹窗打开后nextTick再初始化或调用resize接口返回 500 但是日志里没有详细报错Uvicorn 单 worker 下未捕获异步异常加全局app.exception_handler统一打印堆栈6.2 三个值得分享的独家避坑经验第一个是关于“不可变历史记录”的设计。订单一旦生成单价和总金额立刻快照到订单明细表里绝不能通过外键去关联“作物价格表”反过来计算。因为农产品价格变动频繁明天改价昨天所有订单的金额都要跟着闪变财务那边根本没法对账。做系统时千万别偷这个懒。第二个是 Excel 导出的坑。老板们大概率需要把月度销售台账导成 Excel我用的是openpyxl直接生成.xlsx。这里要注意大数量导出不能用一条查询把所有数据 load 进内存再循环写正确做法是fetchmany分块查询每 500 条写入一次避免内存爆掉和导出超时。第三个是 Vue3 中表格列固定和合计行的组合使用。Element Plus 的el-table用fixed属性固定右侧操作列时如果同时开启合计行合计行只显示当前页数据结算法会被误认为全量数据。我的解决方案是不做前端合计换成后端返回汇总结果在表格底部用单独组件展示“本月累计 / 本年累计”从根源上杜绝数值错误。6.3 上线初期的运营反馈与迭代系统上线头两周是最痛苦又最有价值的阶段。第一周我基本每天盯着使用日志看哪个接口报错最多、哪个页面用户停留时间最长。结果发现“收成批量导入”功能使用率极高但原始 Excel 模板设计得不够灵活农户只填了地块名没填编码导致导入失败率高。我立刻调整了导入逻辑地块名自动匹配映射匹配不到的进入待确认列表而不是整条直接失败。这个改动让批量导入的成功率从 60% 提升到接近 100%。这套系统的迭代方向不是靠想象而是靠观察真实使用行为去驱动。上线一个月后回访时客户那边的库管员甚至主动问我能不能把“本月损耗率”也算出来。这就是一个系统开始真正融入业务流程的信号。7. 个人实操总结与后续扩展想法整套系统从接需求到稳定运行大概花了两个月第一版功能上线只用了三周剩下时间全部在打磨细节和补数据迁移脚本。如果让我重新做一遍我会在需求阶段就把 Excel 导入格式和报表模板确认得更细这两个点后期返工成本最高。我个人的体会有两点。第一农业管理系统本质上是“信任系统”——农户敢把收成录进去客户敢按批次收货老板敢拿报表去安排生产。所以数据的一致性、操作的留痕、历史的不可篡改这几个特性比花哨的图表和一键生成 PPT 重要得多。第二技术选型不用追新但 Vue3 的 Composition API、Pinia、组合式函数这些基建确实是能实打实提升中后台项目开发效率的值得把老项目逐步迁移过来。如果再往后扩展这套系统的自然延伸方向是“订单-农资-成本”的联动销售订单反过来驱动农资采购计划收成记录反过来对比每块地的投入产出比。再进一步可以做简单的损耗分析和价格趋势预测但那些属于“锦上添花”核心的收成和销售闭环已经足够支撑一个合作社或中小型农业公司的日常运转了。最后顺手的小建议真的要把这套系统跑起来别上来先写代码先用一张纸画出从“地头采收”到“回款到账”的完整链路再把每个节点的表格和字段写出来。农业系统的数据链路比互联网产品长得多这步想清楚了后面写代码就是流水线作业。
返回列表