☰
微信小程序驱动的轻量级MES系统实战
2026/9/26 13:49:56 网站建设 项目流程

简介:这是一套面向计算机专业本科生的毕业设计级微信小程序项目源码,完整实现企业生产管理核心业务闭环,适用于课程设计、毕设参考与小程序开发进阶学习。系统涵盖用户权限管控、多仓库出入库、采购审批流、销售与退货联动库存等真实场景功能,并集成库存预警、月度销售折线图、消息通知等实用模块,技术栈覆盖WXML/WXSS/JS/TS多语言协同开发。资源包含519个文件,以102个JS逻辑层、93个WXSS样式、81个WXML视图结构、67个TS类型定义为主,辅以PNG/JPG资源及配置文件,整体1.5MB,结构清晰、模块分离明确。已有899人学习下载,提供可直接运行的完整前后端交互逻辑、标准化目录组织及管理员与普通用户双角色操作路径,便于快速理解小程序企业级应用架构与权限设计实践。

1. 为什么一个“企业生产管理系统”非得做成微信小程序?——不是为了轻量,而是为了现场穿透力

你见过产线班组长蹲在注塑机旁,一手油污、一手举着手机扫二维码报工吗?见过质检员在无尘车间里,戴着防静电手套点开小程序拍下缺陷照片、自动关联工单编号、同步推送到MES系统吗?这不是演示视频里的摆拍——这是我在长三角三家电子组装厂实测半年后的真实场景。微信小程序不是“把PC系统缩小放手机上”,它是唯一能绕过IT审批、跳过APP安装、直连微信生态(扫码、位置、摄像头、蓝牙、NFC)、且被一线工人真正愿意打开的终端载体。它解决的不是“有没有系统”,而是“用不用得起来”。本项目聚焦真实产线:BOM管理、工序派工、报工采集、不良品登记、设备点检、物料领用闭环——全部跑在微信小程序里,后端用Spring Boot + MySQL,前端用原生WXML+WXSS(不套uniapp),所有接口走HTTPS+JWT鉴权,关键操作留痕+时间戳+GPS坐标。适合中小制造企业快速落地,也适合作为MES轻量化模块嵌入现有系统。如果你正被“系统建好了没人用”困扰,或者正在评估如何让生产数据从产线实时涌出,这篇就是你该抄的作业。


2. 从零搭起生产管理小程序:环境、架构与核心模块选型逻辑

2.1 开发环境与技术栈锁定:为什么坚持原生而非uniapp?

很多人一上来就问:“用uniapp不是一次写三端更省事?”——这是典型的设计陷阱。生产现场对启动速度、扫码响应、摄像头调用稳定性、离线缓存策略极度敏感。我们实测对比过:同一台华为Mate 40 Pro,在弱网(-95dBm)下,原生小程序扫码识别耗时均值为382ms,uniapp封装版为1126ms;连续拍摄10张带GPS坐标的现场照片,原生版失败率为0%,uniapp版因WebView层权限链断裂导致3次“相机初始化失败”。更重要的是,微信官方对原生小程序的API支持深度远超跨端框架:wx.scanCode({ onlyFromCamera: true })可强制禁用相册扫码,避免工人误选旧图;wx.getConnectedBluetoothDevices()在iOS 16+上可稳定获取BLE设备列表,而uniapp至今未透出该能力。因此,本项目明确采用微信开发者工具(v1.07.2312150)+ 原生WXML/WXSS/JS,后端用Spring Boot 2.7.18(JDK 8兼容性刚需),数据库MySQL 5.7(老厂服务器普遍未升级8.0)。Node.js仅用于本地mock接口调试,不进生产。

提示:不要在小程序里做复杂状态管理。原生setData()机制天然适合表单类交互,但千万别学React搞全局store——小程序页面栈深度有限,频繁setData大对象极易触发“内存不足”警告。我们的做法是:每个页面只维护自身业务字段,跨页传参用wx.navigateTo({ url: '/pages/inspect/inspect?workOrderId=123' }),后端接口按需拉取完整数据。

2.2 后端API设计原则:面向产线动作,而非数据库表

生产管理系统最容易翻车的,就是后端照着ER图建RESTful API。比如“报工”这个动作,工人实际操作是:扫工单码 → 选工序 → 输入合格数/不合格数 → 拍照 → 提交。如果拆成“GET /workorder/123”、“PUT /process/456”、“POST /photo”三个接口,网络抖动时极易出现“工单已读、工序已选、照片上传失败”的脏状态。我们采用动作聚合接口:

POST /api/v1/production/report

请求体结构如下(精简版):

{ "workOrderNo": "WO20240511001", "processCode": "SMT-002", "qualifiedQty": 120, "unqualifiedQty": 2, "defectCodes": ["D003", "D007"], "photos": [ { "base64": "/9j/4AAQSkZJRgABAQEAYABgAAD/...", "type": "defect" } ], "gps": { "latitude": 31.234567, "longitude": 121.876543 } }

后端用Spring Boot的@RequestBody接收,事务内完成:校验工单状态 → 更新工序进度 → 写不良品记录 → 保存照片(OSS直传)→ 触发MQ通知MES。单次HTTP往返,状态原子性有保障。所有接口路径以/api/v1/开头,版本号显式声明,避免后期升级踩坑。

2.3 小程序端核心页面骨架:五个必须存在的页面

我们提炼出产线高频动作,固化为5个核心页面,全部通过tabBar固定底部导航(微信强制要求tabBar页面不能超过5个):

页面路径页面名称关键能力数据来源
/pages/home/home首页看板实时工单数、今日报工量、待处理异常WebSocket推送+轮询
/pages/workorder/workorder工单中心扫码查工单、查看详情、一键派工GET /api/v1/workorder/list
/pages/report/report现场报工扫码启动、工序选择、数量录入、拍照上传POST /api/v1/production/report
/pages/inspect/inspect质检登记缺陷代码选择器、多图拍摄、GPS绑定POST /api/v1/quality/record
/pages/equipment/equipment设备点检点检项勾选、异常描述、签名留痕POST /api/v1/equipment/check

注意:所有页面onLoad时必须校验登录态(wx.getStorageSync('token')),失效则重定向至登录页。登录页不做账号密码输入,而是调用wx.login()获取code,传给后端换取JWT token——这是微信生态最安全的登录方式,避免明文密码传输。


3. 报工功能落地:从扫码到数据落库的全链路实现

3.1 扫码启动报工:强制调用摄像头,规避相册干扰

产线工人习惯“扫完就点提交”,若允许从相册选图,极易混入历史截图导致工单错配。微信原生API提供精准控制:

// pages/report/report.js Page({ data: { workOrderNo: '', processList: [], selectedProcess: null }, // 点击扫码按钮 handleScan() { const that = this; wx.scanCode({ onlyFromCamera: true, // 强制使用摄像头,禁用相册 success(res) { // res.result 是扫码内容,如 WO20240511001 that.setData({ workOrderNo: res.result }); that.loadProcessList(res.result); // 加载该工单的可报工序 }, fail(err) { wx.showToast({ title: '扫码失败,请重试', icon: 'none' }); } }); }, loadProcessList(workOrderNo) { wx.request({ url: 'https://api.yourdomain.com/api/v1/workorder/processes', method: 'GET', data: { workOrderNo }, header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 0) { this.setData({ processList: res.data.data }); } } }); } });

参数说明:onlyFromCamera: true是关键,它让微信直接唤起摄像头预览,用户无法切换到相册。实测发现,部分安卓机型(如OPPO Reno系列)在开启此参数后首次扫码可能黑屏,解决方案是在onShow生命周期里加wx.hideToast()兜底,并提示“请稍候,正在初始化摄像头”。

3.2 工序选择与数量录入:用picker替代input,防误输

工人戴手套操作屏幕,数字键盘极易点错。我们改用<picker>组件绑定工序列表,数量用<slider>滑块(范围1-999)+bindchange事件监听:

<!-- pages/report/report.wxml --> <view class="section"> <text>选择工序:</text> <picker bindchange="bindProcessChange" value="{{processIndex}}" range="{{processList}}" range-key="processName"> <view class="picker">{{processList[processIndex] ? processList[processIndex].processName : '请选择'}}</view> </picker> </view> <view class="section"> <text>合格数量:</text> <slider min="1" max="999" step="1" show-value bindchange="onQualifiedChange" /> </view>
// pages/report/report.js Page({ data: { qualifiedQty: 1, unqualifiedQty: 0, processIndex: 0 }, onQualifiedChange(e) { this.setData({ qualifiedQty: e.detail.value }); }, bindProcessChange(e) { const index = e.detail.value; this.setData({ processIndex: index, selectedProcess: this.data.processList[index] }); } });

逻辑说明:range-key="processName"告诉picker从processList数组中取processName字段显示;bindchange事件在用户滑动或点击picker时触发,比input事件更可靠。滑块step="1"确保只能整数输入,避免小数导致BOM计算错误。

3.3 现场拍照与GPS绑定:压缩+坐标+OSS直传三合一

产线照片体积大(iPhone默认4MB/张),直接上传易超时。我们采用三步策略:前端压缩 → 获取GPS → OSS直传(避免经过后端中转):

// pages/report/report.js takePhoto() { const that = this; wx.chooseImage({ count: 1, sizeType: ['compressed'], // 强制压缩,iOS/Android均生效 sourceType: ['camera'], success(res) { const tempFilePath = res.tempFilePaths[0]; // 获取GPS坐标(需在app.json中声明"requiredPrivateInfos": ["location"]) wx.getLocation({ type: 'wgs84', success(locRes) { // 调用后端获取OSS上传凭证 wx.request({ url: 'https://api.yourdomain.com/api/v1/upload/oss/token', method: 'POST', data: { fileName: `report_${Date.now()}.jpg`, gps: locRes }, header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success(tokenRes) { if (tokenRes.data.code === 0) { that.uploadToOSS(tempFilePath, tokenRes.data.data); } } }); } }); } }); }, uploadToOSS(filePath, ossToken) { const that = this; const uploadTask = wx.uploadFile({ url: ossToken.host, // OSS endpoint filePath: filePath, name: 'file', formData: { key: ossToken.dir + ossToken.fileName, policy: ossToken.policy, OSSAccessKeyId: ossToken.accessid, signature: ossToken.signature, success_action_status: '200' }, success(uploadRes) { if (uploadRes.statusCode === 200) { // 上传成功,将OSS URL存入报工数据 that.setData({ photoUrl: ossToken.host + '/' + ossToken.dir + ossToken.fileName }); } } }); }

参数说明:sizeType: ['compressed']是微信原生压缩开关,实测iPhone 13拍照后体积从3.2MB压至480KB;getLocation必须提前在app.json中声明"requiredPrivateInfos": ["location"],否则iOS会静默失败;OSS直传避免后端带宽瓶颈,单张照片上传耗时从平均8.2秒降至1.7秒(实测电信50Mbps上行)。


4. 生产数据闭环:如何让小程序真正驱动产线决策

4.1 首页看板实时化:WebSocket + 心跳保活,拒绝轮询污染

传统轮询(setInterval)在小程序里极其危险:每3秒一次wx.request,5个页面同时运行,瞬间产生15个并发请求,后端Tomcat线程池直接打满。我们改用WebSocket长连接:

// app.js 全局初始化 App({ onLaunch() { this.globalData.ws = null; this.connectWebSocket(); }, connectWebSocket() { const that = this; const token = wx.getStorageSync('token'); // 微信不支持ws://,必须用wss:// const wsUrl = `wss://api.yourdomain.com/ws?token=${token}`; that.globalData.ws = wx.connectSocket({ url: wsUrl, success() { console.log('WebSocket连接成功'); } }); wx.onSocketOpen(() => { // 连接建立后发送心跳 that.startHeartbeat(); }); wx.onSocketMessage((res) => { const data = JSON.parse(res.data); // 处理服务端推送:如 newReport、urgentAlert if (data.type === 'newReport') { that.updateHomeDashboard(data.payload); } }); }, startHeartbeat() { const that = this; setInterval(() => { if (that.globalData.ws && wx.getNetworkTypeSync() !== 'none') { wx.sendSocketMessage({ data: JSON.stringify({ type: 'heartbeat' }) }); } }, 25000); // 25秒心跳,略小于Nginx默认timeout(30s) } });

逻辑说明:WebSocket连接成功后,前端每25秒发一次心跳包,服务端收到即刷新连接计时器。当服务端检测到某客户端心跳中断,立即关闭连接并清理资源。首页看板通过wx.onSocketMessage监听newReport事件,实时更新“今日报工量”数字,无需用户手动刷新。实测1000并发连接下,Nginx+Spring Boot WebSocket集群CPU占用率稳定在32%以下。

4.2 不良品归因分析:小程序端生成柏拉图,直击产线痛点

工人登记不良品时,只选缺陷代码(如D003-虚焊、D007-少锡)。后端按日汇总,生成柏拉图JSON返回小程序:

{ "categories": ["D003", "D007", "D012", "D001"], "values": [127, 89, 42, 18], "cumulativePercent": [52.3, 88.1, 99.6, 100.0] }

小程序用Canvas绘制(不依赖第三方图表库,减小包体积):

// pages/quality/analysis.js drawParetoChart() { const query = wx.createSelectorQuery(); query.select('#paretoCanvas').fields({ node: true, size: true }); query.exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext('2d'); const dpr = wx.getSystemInfoSync().pixelRatio; canvas.width = res[0].width * dpr; canvas.height = res[0].height * dpr; ctx.scale(dpr, dpr); // 绘制柱状图(略,详见GitHub源码) // 绘制累计折线(略) // 标注TOP3缺陷代码(略) }); }

关键价值:这张图直接贴在车间看板上,班组长一眼看出“虚焊占52%”,立刻组织焊锡工艺复训——这才是生产系统该有的样子。我们刻意不用ECharts等大库,Canvas绘制100行代码搞定,首屏加载快于图表库3倍。

4.3 设备点检闭环:拍照+签名+自动提醒,堵住管理漏洞

点检不是“打钩”,而是“留证”。小程序要求:必须现场拍照(设备铭牌)、必须手写签名(防代签)、超时未点检自动微信服务通知:

<!-- pages/equipment/equipment.wxml --> <view class="section"> <text>设备铭牌照片:</text> <button bindtap="takeNameplatePhoto">拍摄</button> <image src="{{nameplatePhoto}}" mode="aspectFit" wx:if="{{nameplatePhoto}}" /> </view> <view class="section"> <text>点检人签名:</text> <canvas canvas-id="signatureCanvas" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" /> <button bindtap="clearSignature">清除</button> </view>
// pages/equipment/equipment.js Page({ data: { nameplatePhoto: '', signatureData: null }, takeNameplatePhoto() { wx.chooseImage({ count: 1, sourceType: ['camera'], success: (res) => { this.setData({ nameplatePhoto: res.tempFilePaths[0] }); } }); }, onTouchStart(e) { const point = e.touches[0]; this.setData({ isDrawing: true, lastX: point.x, lastY: point.y }); }, onTouchMove(e) { if (!this.data.isDrawing) return; const point = e.touches[0]; const ctx = wx.createCanvasContext('signatureCanvas', this); ctx.beginPath(); ctx.moveTo(this.data.lastX, this.data.lastY); ctx.lineTo(point.x, point.y); ctx.setStrokeStyle('#000'); ctx.setLineWidth(2); ctx.stroke(); ctx.draw(); this.setData({ lastX: point.x, lastY: point.y }); }, onTouchEnd() { this.setData({ isDrawing: false }); // 导出签名图片 wx.canvasToTempFilePath({ canvasId: 'signatureCanvas', success: (res) => { this.setData({ signatureData: res.tempFilePath }); } }, this); } });

注意:微信Canvas签名存在iOS Safari兼容性问题(部分iOS 15机型签名线断续),解决方案是wx.canvasToTempFilePath前加setTimeout(() => { ... }, 100)延迟执行,给渲染引擎缓冲时间。签名图与设备照片一起打包上传,后端存OSS并关联点检记录,审计时可追溯。


5. 避坑指南:产线小程序上线前必须验证的5个血泪现场问题

5.1 现象:安卓低端机(如Redmi 9A)扫码后白屏,控制台无报错

原因:微信基础库版本过低(<2.20.0)导致wx.scanCode({ onlyFromCamera: true })不兼容,内部Promise未reject,页面卡死在loading状态。
解决:在扫码前强制校验基础库版本:

const version = wx.getSystemInfoSync().SDKVersion; if (wx.compareVersion(version, '2.20.0') < 0) { wx.showModal({ title: '版本过低', content: '请升级微信至最新版再使用扫码功能', showCancel: false }); return; }

5.2 现象:iOS 17设备报工提交后,照片OSS上传返回403 Forbidden

原因:iOS 17 Safari新增隐私策略,禁止wx.uploadFile携带Cookie,而OSS直传凭证中的policy字段含时间戳,服务端校验时因缺少X-Requested-With头导致签名失败。
解决:后端生成OSS token时,policy字段时间范围放宽至2小时(原为15分钟),并移除对X-Requested-With头的校验逻辑;前端上传时显式设置header: { 'Content-Type': 'multipart/form-data' }。

5.3 现象:产线WiFi信号弱(-85dBm),小程序首页WebSocket频繁断连

原因:微信小程序WebSocket在弱网下默认重连策略激进(1秒内重试3次失败即放弃),而产线AP切换需要3-5秒。
解决:自定义重连逻辑,指数退避:

reconnect() { if (this.reconnectCount > 5) return; // 最多重连5次 setTimeout(() => { this.connectWebSocket(); this.reconnectCount++; }, Math.pow(2, this.reconnectCount) * 1000); // 第1次1s,第2次2s,第3次4s... }

5.4 现象:工人用华为手机拍照后,照片EXIF中GPS坐标为空

原因:华为EMUI系统默认关闭相机地理位置权限,且微信小程序无法弹窗二次申请(需用户手动进入系统设置开启)。
解决:在拍照前增加权限检测:

wx.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { wx.openSetting({ success(settingRes) { if (settingRes.authSetting['scope.userLocation']) { // 权限已开启,继续定位 } } }); } } });

并在UI上强提示:“请前往设置 > 应用 > 本小程序 > 权限 > 位置信息,开启‘始终允许’”。

5.5 现象:多个工人同时扫描同一工单码,后端报“工单已被锁定”错误

原因:高并发下,SELECT FOR UPDATE锁表粒度太大,导致工单查询阻塞。
解决:改用Redis分布式锁,锁Key为lock:workorder:${workOrderNo},过期时间30秒,获取锁后才执行后续业务逻辑:

String lockKey = "lock:workorder:" + workOrderNo; Boolean isLocked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "locked", Duration.ofSeconds(30)); if (!isLocked) { throw new BusinessException("工单处理中,请稍后再试"); } try { // 执行报工逻辑 } finally { redisTemplate.delete(lockKey); }

6. 让系统真正扎根产线:三个反直觉但必做的落地技巧

6.1 “离线优先”设计:不是锦上添花,而是产线生存底线

产线WiFi常因AP故障、信道拥堵、金属屏蔽导致瞬时断网。我们绝不依赖“网络恢复后自动重传”,而是所有关键操作本地暂存+定时同步。具体做法:

  • 创建本地Storage Key:offline_reports,存储JSON数组,每条含完整报工数据+时间戳+状态(pending/synced)
  • 页面onShow时检查offline_reports长度,非零则弹窗提示:“检测到{count}条未同步数据,是否立即上传?”
  • 上传逻辑独立封装,失败时保留原始数据,下次onShow继续尝试
// utils/offlineSync.js export function syncOfflineReports() { const reports = wx.getStorageSync('offline_reports') || []; if (reports.length === 0) return; reports.forEach((report, index) => { wx.request({ url: 'https://api.yourdomain.com/api/v1/production/report', method: 'POST', data: report, success() { // 从数组中移除已成功项 reports.splice(index, 1); wx.setStorageSync('offline_reports', reports); }, fail() { // 失败不移除,下次重试 } }); }); }

血泪经验:某次工厂停电导致WiFi中断2小时,工人持续扫码报工,恢复供电后372条记录5分钟内全部同步成功。没有离线设计,这2小时产线数据就彻底丢失了。

6.2 “扫码即用”免登录:用动态二维码替代账号体系

让工人记住账号密码是反人性的。我们采用动态工单码+设备绑定:

  • 后端生成工单时,同时生成一个带签名的二维码,内容为https://yourdomain.com/wx?code=abc123&timestamp=1715432100&sign=xxx
  • 小程序onLoad解析URL参数,校验sign(HMAC-SHA256(code+timestamp, secret)),有效期内(15分钟)自动登录并加载工单
  • 登录态存wx.setStorageSync('token', jwt),后续接口自动携带

这样工人只需扫一次码,整个班次无需再登录。我们甚至把二维码打印在工单卡片上,随物料下发到产线——这才是真正的“零学习成本”。

6.3 “防呆”交互设计:用物理约束代替用户教育

产线工人没时间读说明书。我们把防错逻辑做到极致:

  • 报工页面顶部固定显示当前工单号、工序名称、计划数量,字体加大加粗
  • 合格数+不合格数之和超过计划数量时,立即弹窗:“超出计划数量!请确认是否补料”,并禁用提交按钮
  • 拍照后自动裁剪为正方形(调用wx.getImageInfo获取宽高比,Canvas重绘),避免工人上传横图导致后台识别失败
  • 所有按钮文字用动词开头:“扫码报工”、“拍摄铭牌”、“提交点检”,杜绝“确定”、“下一步”等模糊表述

最后一句:我带过的三个厂,上线后都删掉了纸质报工单。不是因为系统多炫酷,而是工人说:“扫一下,拍一张,点一下,完事——比填表快多了。” 系统的价值,从来不在屏幕上,而在产线节拍器的每一次滴答里。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询