
简介这是一套基于微信小程序的企业生产管理系统完整项目源码主要面向计算机专业毕业设计、课程设计及小程序开发学习者。系统围绕用户、仓库、采购、销售四大模块展开包含用户登录与权限管理、原材料和产品的出入库、库存低于阈值预警、采购计划审批、供应商选择、销售扣减库存、退货重新入库等完整业务闭环页面侧提供首页库存概览、按月销售金额折线图、应用工作台个人中心及消息通知等。资源包共519个文件以js逻辑、wxml页面结构、wxss样式、json配置等小程序核心源码为主另有TypeScript文件、png/jpg图标素材及说明文档整体压缩包仅1.5MB轻量且便于部署学习。目前已有900人浏览学习可用于快速理解企业级管理系统的模块拆分与审批流程。通过该项目可完整掌握从需求分析、页面布局到业务编码落地的全过程是毕业设计和高阶小程序项目的参考蓝本。1. 基于微信小程序的企业生产管理系统为什么我从 PC 端 Web 管理切到了小程序做了几年生产车间的信息化系统最常见的形态一直是 PC 端 Web 后台加扫码枪报工直到有一次在现场盯着车间主任抱着手机翻微信群找异常工单我才意识到问题不在功能缺失而在「能随时随地处理」这件事上。微信小程序的企业生产管理系统本质就是把 ERP/MES 里最核心的报工、派工、审批、物料和看板能力压缩成一套能在手机上跑完闭环的移动端方案——车间主任不用回办公室开电脑操作工扫个码就能报完工老板在外面也能看到实时产量。这个方案的受众很明确想做毕设选题的在校生这套系统的完整度足够支撑一篇合格的设计与实现论文、小企业的信息化负责人不想上重型 MES 又想先跑通移动化、以及正在做微信小程序外包开发的工程师。标题里「设计与实现」四个字意味着你要交付的不只是能跑的界面还包括数据库设计、接口设计、角色权限模型和部署落地。这篇文章会直接告诉你选型怎么定、后端接口怎么设计、小程序端哪些页面最核心、报工和审批流程怎么做不出错、以及我踩过的几个真坑。2. 生产管理系统的技术选型与总体架构原生小程序还是 uni-appSpring Boot 还是 Node2.1 为什么「前后端分离 微信原生小程序」是这套系统最稳妥的底盘做小程序生产管理系统第一步不是写代码是选型。生产管理类系统的核心特征是数据结构复杂BOM、工单、物料批次、表单密集报工、领料、质检、并发集中在同一时间点上班和下班前后是报工高峰。这种特性决定了你不需要很花哨的前端框架但需要一个数据建模能力和事务支持都够硬的后端。小程序端我建议直接用微信官方原生框架而不是 uni-app 或 Taro。原因有三第一原生框架调蓝牙扫码、调相机拍照、拿微信运动这类原生能力时最省事不需要去匹配各家小程序的差异化 API第二生产管理系统用到的组件无非是表单、列表、日期选择、扫码原生组件足够H5 化带来的跨端收益在这里完全用不上第三原生小程序的调试工具对性能和报错定位更直接对于要写「设计与实现」文档的场景原生框架的代码结构更容易讲清楚。如果你已经在用 Taro 或 uni-app也不是不能做只是在写调用 wx.scanCode 这类接口时会多一层兼容代码要维护。后端选 Spring Boot 是常见做法理由很直白Spring Boot 的注解式开发能快速把 Controller/Service/Mapper 三层立起来配合 MyBatis-Plus 可以少写大量基础 SQL。如果用 Node.js 的 Express 或 NestJS写接口速度也很快但在做复杂事务比如报工要同时扣物料库存、更新工单进度、写入计件工资的时候Spring 的 Transactional 声明式事务比在 Node 里手动控制 session 或事务边界要可靠得多。数据库就选 MySQL生产管理系统没有你想象中那么大的数据量单表几百万行的工单记录对 MySQL 加合理索引完全够用。2.2 把系统拆成「小程序端 网关 业务服务 数据层」四块各层各自管什么事我一般会把系统拆成这样四个部分你画架构图也按这个层次画答辩或汇报时逻辑最顺。小程序端只负责三件事渲染页面、收集表单数据、调 wx.request 发请求。它不直接碰数据库也不写业务校验——所有和钱、库存、权限有关的判断必须放在后端。网关层在起步阶段不需要引入 Spring Cloud Gateway 那套重家伙直接用 Spring Boot 的拦截器HandlerInterceptor统一做三件事登录态校验解析 Token、操作日志记录、接口权限判断。等以后要接多个端比如后续加一个 PC 管理端再升级成独立网关也不迟。业务服务层按领域拆包production工单、report报工、approval审批、material物料、system用户与角色。每个包保持 controller/service/mapper 三层避免不同模块互相直接调 service 造成耦合。数据层就是 MySQL 加 RedisRedis 只用来存登录态和工位设备的 Token 映射不存核心业务数据。这里有一个容易犯的错把所有表的增删改查都做成通用接口然后在小程序端做权限控制。权限必须放在后端和接口绑定。举个例子报工接口 POST /api/report 必须校验「当前用户是否是该工单的指定操作工」、「该工位是否在有效期内」。这个校验放在小程序端会被绕过——用 Postman 直接调接口就失效了这正是生产管理系统最容易出现的数据安全隐患。2.3 角色与权限模型这是生产管理系统区别于普通小程序的核心差异点生产管理系统的角色设计要比普通应用复杂最少要有四类角色管理员系统配置和用户管理、计划员创建工单、调整排程、车间操作工扫码报工、发起领料、车间主任审批报工、处理异常。还有一些企业会把质检验收单独拆一个角色先按四类做后续加角色时再扩展。权限模型用基于角色的访问控制RBAC两张核心表角色表 sys_role、用户角色关联表 sys_user_role再加一张菜单/按钮权限表 sys_permission。接口权限用自定义注解 RequirePermission(report:submit) 标注在 Controller 方法上拦截器里根据当前用户的角色去 Redis 查权限集合判断放行。这里要特别处理的是「数据范围」问题也就是一个角色能看哪些数据。操作工只能看分配到自己的工单车间主任能看整个车间的管理员能看所有车间的。这个不能用接口级权限解决需要在工单查询 SQL 里拼车间和用户维度来限定。我是通过 MyBatis-Plus 的拦截器实现的在查询工单列表时自动追加where dept_id 当前用户的部门id如果当前角色是管理员则不加这条。这样做的好处是业务代码里不需要每个方法都手动传部门条件不会漏。3. 后端核心设计与实现从工单下达到报工入库的完整接口闭环3.1 数据库设计要点工单、报工、审批三张核心表的字段和关系数据库是整个系统的地基。生产管理系统的核心流程是计划员创建生产工单 → 工单关联产品和物料清单 → 操作工按工单报工 → 报工单进入审批 → 审批通过后更新工单实际产量和员工计件工资。按这个流程核心表我建议这样设计生产工单表 production_order核心字段order_no工单号、product_id产品 ID、plan_qty计划数量、finish_qty完成数量、status状态0待开始/1进行中/2已完成/3已暂停、workshop_id车间 ID、plan_start_time、plan_end_time。工单状态是整个系统的流转枢纽所有其他表都在围绕它做更新。报工表 report_record核心字段order_id关联工单、user_id报工人、workstation_id工位 ID、report_qty本次报工数、qualified_qty合格数、report_time报工时间、status0待审/1通过/2驳回。注意合格数要单独记录因为实际生产中报工数不等于合格数的情况太常见了。审批表 approval_record核心字段biz_type业务类型报工/领料/异常、biz_id业务单 ID、approver_id、approval_comment、approval_result、create_time。把审批做成通用表的好处是后续扩展请假、领料审批时不用再建表。我见过很多项目把审批状态直接写死在业务表里导致要查某个人审批过的所有记录时必须分别查报工表和领料表非常痛苦。关联关系上报工表和工单表是多对一审批表和报工表是多对一。这三张表设计好后整个系统的数据流就立住了。3.2 用 Spring Boot MyBatis-Plus 实现工单创建与分页查询接口工单创建接口是最典型的「计划员操作」要处理的细节是唯一工单号的生成和初始状态写入。工单号不能自增因为生产现场要看单号识别业务含义。我一般用日期加车间编码加流水号20250616WORKSHOP01001用 Redis 的 INCR 命令按天自增避免并发下重复。RestController RequestMapping(/api/order) public class ProductionOrderController { PostMapping(/create) RequirePermission(order:create) public Result createOrder(RequestBody Valid OrderCreateDTO dto) { // 生成工单号yyyyMMdd 车间编码 当日流水号 String orderNo orderNoGenerator.generate(dto.getWorkshopId()); ProductionOrder order new ProductionOrder(); BeanUtils.copyProperties(dto, order); order.setOrderNo(orderNo); order.setStatus(0); // 待开始 order.setFinishQty(0); orderService.save(order); return Result.ok(order.getId()); } }这段代码的关键在两点一是RequirePermission注解保证了只有计划员角色能调用二是工单号生成器独立成一个组件后续接排程系统时可以直接复用。工单分页查询接口要支持多条件组合筛选按工单号模糊搜索、按状态筛选、按时间范围筛选。这里有个常见的查询性能问题如果不用 MyBatis-Plus 的条件构造器手写 XML 很容易因为 if 拼接出错。Override public PageResultProductionOrder queryOrderPage(OrderQueryDTO dto) { PageProductionOrder page new Page(dto.getPageNum(), dto.getPageSize()); LambdaQueryWrapperProductionOrder wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(dto.getOrderNo()), ProductionOrder::getOrderNo, dto.getOrderNo()); wrapper.eq(dto.getStatus() ! null, ProductionOrder::getStatus, dto.getStatus()); // 数据权限自动追加非管理员只能查本车间 dataScopeHelper.apply(wrapper, ProductionOrder::getWorkshopId); wrapper.orderByDesc(ProductionOrder::getCreateTime); orderMapper.selectPage(page, wrapper); return new PageResult(page); }这里注意like和eq的第一个参数都是条件判断传入的筛选值为空时该条件不参与拼接。这个写法比用字符串拼接 SQL 安全得多能避免常见的 SQL 注入和空值 NPE 问题。数据权限那行dataScopeHelper.apply是核心它保证操作工打开工单列表时只能看到自己车间的数据即使他手动修改了接口参数也绕不过去。3.3 报工接口事务、并发和状态机的三重合围报工是整个系统里最容易出问题的接口没有之一。它同时牵扯三件事工单状态更新、报工记录写入、计件工资统计。如果不用事务包裹任何一个环节失败都会产生数据不一致——比如报了工但工单完成数量没增加。下面这段代码给出了一个完整的报工实现核心逻辑Transactional(rollbackFor Exception.class) public synchronized SubmitReportResult submitReport(ReportSubmitDTO dto) { // 1. 校验工单状态只有进行中才能报工 ProductionOrder order orderMapper.selectByIdForUpdate(dto.getOrderId()); if (order null || order.getStatus() ! 1) { throw new BusinessException(工单不存在或当前状态不可报工); } // 2. 写入报工记录 ReportRecord report new ReportRecord(); report.setOrderId(dto.getOrderId()); report.setUserId(CurrentUserHolder.getUserId()); report.setReportQty(dto.getReportQty()); report.setQualifiedQty(dto.getQualifiedQty()); report.setStatus(0); // 待审批 reportMapper.insert(report); // 3. 累计工单完成数量注意并发安全 int updated orderMapper.increaseFinishQty(order.getId(), dto.getQualifiedQty()); if (updated 0) { throw new BusinessException(工单完成数量更新失败请重试); } return new SubmitReportResult(report.getId()); }这段代码里有三个层面的保护。第一层是Transactional保证三个数据库操作要么全成要么全败。第二层是selectByIdForUpdate用行锁把工单记录锁住两个操作工同时报同一张工单时后到的会等待避免完成数量被覆盖。第三层是乐观更新increaseFinishQty的返回值如果更新影响行数为 0说明中间状态被改动了强制报错。这三个机制叠加报工接口在并发场景下才站得住。报工之后要触发审批流程。这里不用引入 Activiti 或 Flowable 这种重量级工作流引擎用一张审批表加状态判断就够了。报工状态为 0 时车间主任能看到待审批列表主任点击通过后后端更新报工单状态为 1同时累加工单合格数量并写入月度计件工资临时表。如果你的场景涉及多级审批比如超过 500 件需要厂长二次审批可以把审批人字段设计成approver_role加next_approver_role一个 while 循环就能驱动流转。3.4 审批接口的两种实现对比状态位更新和通用审批引擎审批接口无非就是「改状态 记日志」从简实现时一张表就够了。PostMapping(/approve) RequirePermission(report:approve) public Result approve(RequestBody ApproveDTO dto) { ReportRecord report reportMapper.selectById(dto.getReportId()); if (report.getStatus() ! 0) { throw new BusinessException(该报工单已被处理); } // 更新报工单状态 report.setStatus(dto.getApproveResult() 1 ? 1 : 2); reportMapper.updateById(report); // 写入审批记录通用表 ApprovalRecord approval new ApprovalRecord(); approval.setBizType(report); approval.setBizId(report.getId()); approval.setApproverId(CurrentUserHolder.getUserId()); approval.setApprovalResult(dto.getApproveResult()); approval.setApprovalComment(dto.getComment()); approvalMapper.insert(approval); return Result.ok(); }通用审批引擎适合哪种场景呢比如一张报工单要流转四级审批每一级审批人角色不同驳回后还得回到上一级重新走流程。这种场景把审批人都写死在代码里会非常痛苦抽个通用引擎值得。但要注意通用引擎会引入一张流程定义表和一张流程实例表实现成本约莫多一天工时对毕设或小企业系统来说可能不划算。我的建议非常明确只有一级审批用状态位直接改清清爽爽业务规则明确要两级以上再上引擎。4. 微信小程序端的页面与逻辑实现从登录到扫码报工的关键代码4.1 微信登录与自建账号体系的绑定为什么不能只用微信的 openid微信小程序有一套自己的登录体系核心流程是wx.login()拿 code后端拿 code 换 openid。但生产管理系统必须绑定内部账号体系原因是车间里的大屏、扫码枪、PC 端报表都要共用一套用户数据不能只有微信侧认识这个用户。而且权限模型是基于角色的微信 openid 和角色没有天然绑定关系。我的做法是首次进入小程序时调wx.login()获取 code发给后端换取 openid。后端查用户表 sys_user 里有没有绑定这个 openid如果没有跳转到绑定手机号页面。用户输入手机号和密码后端校验通过后把 openid 和用户 ID 绑定生成自定义 Token 返回给小程序。之后的请求全部用这个 Token 来鉴权不再调用微信接口。// 小程序端登录逻辑片段 login() { wx.login({ success: async (res) { const loginRes await request({ url: /api/auth/wxlogin, method: POST, data: { code: res.code } }); if (loginRes.data.needBind) { this.showBindModal(); // 未绑定的账号弹窗绑定 } else { wx.setStorageSync(token, loginRes.data.token); } } }); }注意一个坑wx.login()生成的 code 只能用一次五分钟后过期。如果后端网络超时导致换 openid 失败前端不能直接重发同一个 code必须重新调wx.login()拿新 code。这个细节在很多项目里都出过线上事故写代码时要注意错误重试机制。Token 我用的是 UUID 作为随机字符串存 Redis有效期七天。如果要更严谨的生产环境可以换 JWT但 JWT 的缺点是服务器端无法主动失效用户被删了 Token 在到期前还能用。生产管理系统对离职员工的权限回收要求高我倾向用 Redis 存储式 Token。4.2 扫码报工scanCode 接口、生产工单状态校验与并发提示扫码报工是小程序端最高频的操作也是这个系统的「核心交互」。操作工在工位上扫产品流转卡上的二维码系统解析出工单号自动带出工单信息操作工填写报工数量和合格数量提交完成报工。// 扫一扫报工页面核心逻辑 scanAndLoadOrder() { wx.scanCode({ onlyFromCamera: true, success: (res) { const orderNo res.result; this.loadOrderDetail(orderNo); }, fail: (err) { if (err.errMsg.includes(cancel)) return; wx.showToast({ title: 扫码失败请重试, icon: none }); } }); }, loadOrderDetail(orderNo) { request({ url: /api/order/detailByNo, data: { orderNo }, success: (res) { if (res.data.status ! 1) { wx.showToast({ title: 当前工单不可报工, icon: none }); return; } this.setData({ order: res.data, visible: true }); } }); }onlyFromCamera: true是为了强制走摄像头而不是从相册识别防止操作工胡乱扫旧图片。扫码成功后先在前端做一次工单状态校验状态不是「进行中」直接拦截省去用户等后端返回后才报错的时间。但这只是第一道防线最终判断权在后端报工接口里上面后端代码里已经有status ! 1的校验前端拦截只是为了提升体验。报工结果回显也有讲究。提交成功不能只弹一个「报工成功」的 Toast 然后清空页面建议把本次报工数量、累计完成数量、当前工单进度比如已完成 68%都显示出来。操作工需要这个反馈来确认自己没报错数车间主任也需要这个数字作为班前会的数据依据。我给的这个页面还要显示工单剩余数量这样操作工能直观判断是否接近完工。4.3 车间看板页面用 WebSocket 还是定时轮询车间主任和老板在小程序端最想看的就是实时产量。这里就遇到一个选择题WebSocket 推还是定时轮询。我的建议很实际第一版直接用定时轮询每隔 30 秒调一次汇总接口完全够用实现简单且不会因为弱网导致连接断开后数据不同步。等有「产量大屏」这种强实时需求时再单独接 WebSocket 通道。轮询接口设计时注意一个优化点小程序切到后台后JS 定时器会被微信暂停。要在onShow时重新启动定时器在onHide时清除。否则会出现「页面退到后台再回来数据还是十分钟前的」这种血泪问题。// 看板页面的轮询逻辑 onShow() { this.startPolling(); }, onHide() { this.clearPolling(); }, startPolling() { this.pollingTimer setInterval(() { this.loadDashboardData(); }, 30000); }, clearPolling() { if (this.pollingTimer) { clearInterval(this.pollingTimer); this.pollingTimer null; } }Dashboard 数据接口返回当日总产量、各产线完成率、当前进行中的工单列表这三块数据。SQL 上注意用日期索引避免全表扫描。很多生产系统的大屏接口越写越慢原因就是没对create_time和order_id加组合索引日积月累后一条简单查询要跑好几秒。4.4 表单与列表的性能细节setData 的粒度控制这个坑新手机上看不出来老手机上非常明显。微信小程序的setData是逻辑层到渲染层的全量数据传递如果一次性 set 一个几百条记录的数组低端安卓手机会明显卡顿。生产管理系统的报工记录列表按周查询动辄上千条直接把全量数组 set 上去会卡到操作工骂人。处理办法有两个层面。第一层是分页加载onReachBottom 时加载下一页数据用this.setData({ list: this.data.list.concat(newList) })做追加。第二层是只更新变化的数据比如只更新某一条记录的状态用this.setData({ [list[ index ].status]: 1 })这个路径式更新避免整个列表重新渲染。在报工审批列表中我通常会给每条记录加一个「通过」按钮点击后只更新这一条的数据效果比刷新整个列表流畅得多。还有一个技巧列表项的data里不要塞大字段。比如报工记录的备注字段可能有两三百字如果列表接口把它全查出来传输和渲染都有损耗。列表接口只返回 id、工单号、数量、时间和状态详情接口再返回备注全文。这样列表首屏速度会快不少。5. 微信小程序生产管理系统最常见的 6 个坑从权限绕过到数据库锁等待5.1 报工并发导致完成数量被覆盖数据直接出错现象两个操作工同时扫同一张工单各自报工 100 件工单完成数量不是 200 而是 100。原因是报工接口先查工单当前完成数再加本次报工数去更新两个请求都查到 100最终写回的都是 100。解决严格按上面代码的做法用selectByIdForUpdate对工单行加锁或者用update ... set finish_qty finish_qty {qty}的 SQL 原子累加不要先查再写。另一个办法是加 Redis 锁但行锁在这个场景足够且更简单。5.2 用户角色权限被绕过小程序端隐藏按钮不是真正的权限控制现象有人用 Postman 直接调后端报工审批接口绕过了前端按钮的隐藏逻辑把不是自己车间的报工单也给审批了。原因是权限判断只做在小程序端wx:if控制按钮显隐后端接口没有做鉴权。解决后端加拦截器统一校验 Token 和角色权限业务接口用RequirePermission注解做不可绕过的判断。记住一个原则前端限制只是体验后端校验才是底线。5.3 工单状态不一致报工成功但工单已完成数量没加上现象报工单状态是待审批但工单的完成数量一直是 0车间主任查产量时发现对不上账。原因是报工接口没有加事务报工记录写入了但后续更新工单完成数量的操作手写 SQL 报错被吞掉。解决在接口上加Transactional(rollbackFor Exception.class)并且不要手动try-catch吞异常。如果抛出的是受检异常记得配rollbackFor否则默认情况下事务是不会回滚的。5.4 日期时间控件在 iOS 上显示异常格式字符串不是东八区现象同一套代码在安卓上报工时间是 16:00在苹果手机上显示 08:00。原因是 iOS 的 JavaScript 引擎对2026-06-16 16:00:00这种带横杠的字符串解析是当作 UTC 时间处理的安卓没问题而 iOS 直接减了 8 小时。解决后端返回时间戳数字例如1750060800000而不是格式化字符串小程序端用自己封装的日期工具去格式化。涉及日期的字段全部统一用 long 型时间戳传输界面展示层再转字符串可以避免很多时区和格式的玄学问题。5.5 扫码枪扫码后跳转页面返回时数据丢失现象操作工扫了 A 单报工后返回列表页再点列表进 B 单详情时页面是空白的。原因是用wx.navigateTo压栈跳转页面栈层级接近十层时新开的页面拿不到参数或者页面复用导致onLoad不触发。解决报工确认页用wx.redirectTo代替navigateTo不让页面栈无限堆积列表到详情传参不要只靠 URL 参数把必要数据放进全局变量或 storage 在页面加载时读取如果必须保留中间页状态用wx.navigateBack配合eventChannel传值。5.6 数据库连接池被耗尽系统整体无响应现象上午 9 点上班时系统突然所有页面都打不开后端日志报connection pool exhausted。原因是把 Redis 的读取也走了主数据库连接池加上报工时间段的所有工位同时提交请求连接用完。解决给 Redis 操作单独配置连接池参数数据库连接池根据并发量调整maximum-pool-size并在耗时操作比如文件上传中释放数据库连接。另一个常见做法读多写少的统计接口用 Redis 缓存结果给数据库连接池留出余地。6. 跑通后的验收与进阶接口压测、扫码模拟、权限回归这套验证流程值得照着做系统基本跑通后的第一步是用脚本模拟报工并发。我用 Jmeter 或简单的 Python 脚本开 50 个线程同时调报工接口重点盯两件事一是工单完成数量是否正确累加二是响应时间有没有明显退化。这一步能提前暴露事务和锁相关的问题等到车间现场再发现就晚了。页面联调阶段我习惯在微信开发者工具里开启「不校验合法域名」但真机预览时必须关掉。报工扫码这块模拟器里的扫码功能是可以自定义的开发者工具支持在「编译模式」里配置模拟扫码结果把orderNo塞进去就能测试扫码后的流程。真正到了真机阶段用一张打印出来的二维码贴在产品包装盒上走完整流程发现问题当时解决。还要做一遍权限回归。生产管理系统的权限调整很频繁新入职操作工、离职员工、领导关系变更都会改权限。用管理员账号登录把角色从车间主任改成操作工然后反复测试各个页面是否能正确收起不该出现的按钮同时用 Postman 测一下敏感接口是否被正确拦截。这个流程我每次发版前都要跑一遍花费半小时能挡掉大部分权限事故。最后值得记录的一个习惯我把线上环境的报错日志接到服务器专门的日志目录按天切割每次客户打电话过来说「哪里不对」时先翻日志再问话。这套系统的核心价值其实不在于用了多新的技术栈而是把纸质工单变成了电子化流程让每个工单从下发到报工的每一步都有据可查。我做这套系统最大的教训是什么是把全部前端逻辑交给小程序、不在后端做校验后来被绕权折腾到加班查数据。从那以后所有关键操作都在后端兜底再也没有因为权限问题出过事。希望这篇笔记能帮你在自己的生产管理系统里少踩几个我踩过的坑。本文还有配套的精品资源点击获取