五金车间最像打仗的地方不是生产线,而是计划员那张桌子。我刚做完的这套基于 node.js 和 vue 的五金工厂车间生产计划管理系统,想解决的正是这个问题:把销售订单变成可执行的车间计划,再把计划和执行之间的信息差抹平。做之前我以为最难的是排产算法,做之后才发现最难的是让计划员、车间主任和工人对同一组数据有一个统一的认识。这篇文章我会从业务背景、技术选型、模块设计、数据库与后端细节、前端落地,再到上线踩坑,完整复盘一遍,适合正在考虑自研或选型工厂管理系统的团队参考。
1. 五金车间的排产痛点:Excel、ERP 之间的那块空地
1.1 五金车间生产管理的业务特征
五金工厂和电子厂、汽配厂的管理重点差别很大。五金件常见的工艺是冲压、拉伸、机加工、抛光、电镀或者氧化,工序数量不多,但它是典型的多品种、小批量、插单频繁的业态。一个车间里同时可能有几十个在制订单,每一张订单又可能拆成三五个规格,每个规格对应一套模具或者一个加工程序。
这种业态下,真正难管的地方反而不是工艺,而是“什么时候开哪台机、用哪套模具、做多少个、做完给谁”。冲压车间换一次模可能需要半小时,如果排程不合理,一天的有效产出直接打六折。机加工车间则要错开不同订单对同一台设备的争抢,但交期晚一天客户就催。
我接触过不少五金厂,计划员手上同时维护三样东西:一张 Excel 总排程表、一块写在白板上的插单信息、以及手机里各种微信群里的临时通知。Excel 表本身没有约束力,插单一进来,整张表就要重新拖一遍;白板上的内容没人归档,到了月底就说不清上个月到底做过什么。
1.2 现有工具的断层:Excel 灵活但不可控,ERP 重但适配差
不是所有人都在用 ERP,用 ERP 的五金厂也未必用好。Excel 的优势是灵活,任何规则都能硬排进去,但它的劣势同样致命:交期撞车它不报警,库存不足它不提示,两个计划员各排各的版本也没有冲突检测,最后装进车间主任脑子里的排程,永远和 Excel 里那个版本对不上。
ERP 是另一种极端。它适合业务规范、流程固定的大型厂,能管到采购、财务、成本、MRP 这些环节。但中小五金厂的流程往往没有那么标准,很多物料连编码都没统一。上 ERP 意味着车间每个动作要被录进系统,操作繁琐、界面复杂、权限死板,工人一抗拒,系统里的数据很快就失真了。
中间其实有一块空地:一个只负责“订单 → 计划 → 工单 → 报工 → 看板”的管理系统,比 Excel 多约束、多实时性,比 ERP 轻量、贴近车间真实习惯。这套 node.js + vue 的系统定位就在这个位置,不碰财务和总账,把车间计划这条主线的信息同步问题解决掉。
1.3 写代码前最重要的一件事:先把数据理清楚
很多人拿到这个需求第一反应是建表写接口,但实操中第一个拦住项目的往往是基础数据。物料编码有没有统一?BOM 是不是最新版?工艺路线里的工时是拍脑袋还是实测过?
我开始时吃过这个亏。厂里仓库管料叫“黄铜套”,工艺那边叫“HPb59-1 毛坯”,采购单上又是另一个名称,一套系统里三种叫法,等于没上系统。后来项目组做的第一件事,是把所有常用物料和产品编码重新编了一遍,编好规则再导入系统主数据。
如果你也要做类似系统,我的建议是:先花一到两周梳理物料编码、BOM、工序和设备的清单,再动技术框架。数据是排程的地基,地基歪了,后面所有报表都是数字游戏。
2. 为什么这个系统押注 Node.js + Vue:一次完整的技术选型复盘
2.1 前后端同构对中小项目团队的意义
技术选型那天,会议室里其实吵过一轮。有人提议 Spring Boot + Vue,理由是 Java 稳定、招人容易;也有人想用 Python + Vue,图开发快。最后定的还是 Node.js + Vue,这是基于团队规模和使用场景综合考量后的结果。
最直接的收益是前后端同构。项目组就三四个人,前端用 Vue,后端用 Node.js,两边都是 JavaScript/TypeScript,一个全栈工程师就能独立把一个模块从接口写到页面。Java 项目常见的前端工程师看不懂后端、后端工程师不愿意碰前端的隔阂,在这个项目里少了很多。
加上 Vue 的生态确实适合做管理后台。Element Plus 组件库把表格、表单、弹窗、标签页这些高频元素都封装好了,开发效率比从零写快非常多。这类车间管理系统本质上是大量 CRUD 和状态流转的组合,Vue 的响应式数据模型和组件复用能力正好踩在这个需求点上。
2.2 Node.js 的非阻塞模型恰好匹配车间高频小请求
车间上报进度的场景很特殊:数量大、单次请求小、频率高。一台设备每完成一批就要点一次“完工”或者用扫码枪扫一次工单号,设备一多,服务器面对的是每秒几十上百个轻量请求,每个请求业务逻辑不重,但并发量不小。
Node.js 基于事件循环和非阻塞 I/O,对这种 I/O 密集的小请求处理起来很顺手,单进程能扛住比传统同步模型高很多的并发连接。再加上 Socket.io 做 WebSocket 推送,看板数据可以直接从服务端推到车间大屏,不需要轮询,实时性也更有保障。
要注意的是,这不代表 Node.js 万能。如果系统里涉及大量复杂的报表聚合计算、多表关联的复杂事务,Node.js 写起来会比 Spring Boot 更费劲。所以在架构上我把重统计接口都放到独立服务里,配合 Redis 缓存,主业务接口保持轻量。
2.3 和 Spring Boot、Python 方案的真实对比
不少同行问为什么不直接用 Spring Boot。能用,而且很多工厂项目确实这么选。但如果你的团队本来就不是 Java 背景,为了一个小系统专门去搭一套 Java 技术栈,Maven、JVM 调优、类库版本冲突这些维护成本会很快吞掉开发效率。Spring Boot 的强项是大团队、长周期、重事务,我们的场景显得杀鸡用牛刀。
Python + Django 也评估过。开发原型确实快,管理后台能现成生成,但实时推送和并发处理没有 Node.js 顺手,而且一门后端语言和前端割裂,小团队维护起来两头都要管。
我把三套方案的差异整理成了表格,方便大家在类似项目里做判断。
| 技术栈 | 优势 | 在这个场景下的劣势 |
|---|---|---|
| Spring Boot + Vue | 事务成熟、大团队规范、资料丰富 | 部署重、开发节奏慢、团队要求高 |
| Python + Vue | 原型快、后端起手容易 | 实时推送一般、前后端异构 |
| Node.js + Vue | 同构、轻量、并发好、部署简单 | 复杂事务要小心、生态偏前端 |
这个项目最终选择 Node.js + Vue,不是因为它比其他方案高级,而是它和最真实的资源情况匹配:团队小、周期紧、并发特点明显、部署环境只有一台服务器。
2.4 环境搭建时容易被忽略的版本细节
Node.js 版本建议直接选 LTS。项目里用的是 18.20.4,生产环境不要追最新版,等一个版本在社区里沉淀半年以上再上,能少踩很多坑。Vue 这边用的是 Vue 3 + Vite + Element Plus,开发依赖用 pnpm 管理依赖,比 npm 更快也更省磁盘空间。
开发环境搭建的完整动作大概是这样的:
# 安装 nvm,用它管理 Node 版本 nvm install 18.20.4 nvm use 18.20.4 node -v # 创建 Vue 项目 npm create vue@latest npm install npm run dev这里有个容易踩的小细节:Vue 3 的响应式包装和 Vue 2 差别很大,如果团队之前熟的是 Vue 2 选项式 API,接手 Vue 3 组合式 API 需要一点适应期。后面讲到前端组件时我会再展开。
3. 业务模块拆解:计划、工单、物料、看板是如何串成一条闭环的
3.1 主数据:物料编码、BOM 与工艺路线
系统里的主数据是后面所有模块的地基。我拆成三张核心表来聊:
产品表存产品编码、名称、图号、规格、版本号。五金件经常会改图纸,产品版本不管理好,工单下发后车间做的可能还是老版本,这个问题我在项目里见过不止一次。所以每个产品必须带版本号,工单生成时把产品版本快照到工单上,避免后续改版把历史工单也改了。
物料清单,也就是 BOM,记录每个产品需要哪些原材料、标准件,数量是多少。注意五金件会有边角料和损耗率,我在 BOM 里专门加了一个 plan_loss_rate 字段,冲压的首件调试废料就通过这个字段消化,而不是让工人把损耗记到成品头上。
工艺路线是每类产品经过的工序集合,包括工序号、工序名、使用设备类型、标准工时、每小时产出等。排程能排多细,完全取决于工艺路线维护到什么程度。至少要维护到“工序级”,不然甘特图只能排到订单,排不到设备。
3.2 计划生成逻辑:净需求计算和合并规则
计划员从销售订单勾选一批订单,点击“生成计划”,系统自动算出净需求。公式不复杂,但每项含义要跟业务确认清楚:
净需求 = 订单数量 - 可用库存 - 在制订单对应数量 - 已下达工单未完工数量这里“在制订单对应数量”是指已经进入生产但还没完工入库的数量,如果漏算,计划员会把已经生产的量再排一遍,造成重复生产。
算出净需求后,系统按交期和客户优先级排序。排列组合这里有个优化点:尽量把批量小、同材质同规格的产品合并成一张工单。五金车间最大的浪费在换模,一张工单做 50 件和做 500 件,准备时间可能差不多,所以排程时优先合并相同产品编号、相近交期的需求,减少模具切换次数。
3.3 排程与工单下发:从计划到可执行的过程
计划生成后进入排程界面,我用的是前端甘特图组件。横轴是设备或者产线,纵轴是时间,工单就是一个方块,计划员可以拖动调整开工时间。系统排程时会自动检查设备占用冲突,时间重叠了会标红提示。
排程完成后,计划员选中工单点击“下发”。这一步我特意做成了有条件的操作:系统先跑一次物料齐套检查,缺料的工单不能下发。这个约束在纸质时代是计划员凭记忆做的,系统化之后变成了硬规则,实际运行中帮业务方挡住了很多次缺料开工导致停工等待的问题。
工单核心字段包括工单号、产品、数量、计划开始时间、计划结束时间、使用的工序路线、优先级、状态。工单号我用的是独立生成的业务编号,比如 WO + 年月日 + 流水号,而不是数据库自增 ID,这个习惯后面帮了大忙,打印条码、跨部门沟通时都能一眼识别。
3.4 物料需求计算与领料退料
工单下发后,系统根据 BOM 自动展开物料需求:工单数量乘以 BOM 里每个物料的单件用量,再乘上计划损耗率,得到物料需求量。和库存实时比对,生成缺料清单。
五金厂的领料比电子厂粗放,经常是车间一次性领一批材料放到工位,月底再退。所以这里我没有按“领料单严格倒冲”的精细模型,而是采用“大领大退”模式:车间可以批量开领料单,完工后退回余料,系统记录一个物料批次去向,做到批次可追溯即可。对五金车间来说,追溯粒度控制在“工单 + 物料批次”级别就够用了,太细反而增加工人操作负担,系统会被抵制。
3.5 车间报工与进度看板的实时刷新
报工是整个系统使用频率最高的功能,工人扫工单条码后进入报工页,输入本次完工数量和不良数量,点击提交。系统累加到工单的 completed_quantity 和 defective_quantity 上,同时计算剩余量。
每次报工数据变化后,服务端通过 WebSocket 把工单进度和设备状态推到车间看板。看板大屏只显示三块内容:今天的计划任务列表、每张工单的进度条、以及设备状态(运行中/空闲/故障)。不要放太多花哨图表,车间里真正有用的就是这几项。
4. 后端与数据库的严谨性设计:生产数据不能靠“感觉”
4.1 核心表结构设计与字段注意事项
数据库用的是 MySQL 8.0,字符集 utf8mb4。核心表我列一下:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| products | 产品主数据 | id, product_code(唯一索引), name, drawing_no, version, unit |
| bom_items | 物料清单 | id, product_id, material_id, qty, plan_loss_rate, is_key_material |
| process_routes | 工艺路线 | id, product_id, seq_no, process_name, device_type_id, standard_hour, output_per_hour |
| work_orders | 工单 | id, work_order_no(唯一索引), product_id, qty, completed_qty, defective_qty, status, version |
| work_order_logs | 报工日志 | id, work_order_id, user_id, report_qty, defective_qty, created_at |
| materials | 物料 | id, material_code(唯一索引), name, spec, safe_stock, stock_qty |
| inventory_transactions | 库存流水 | id, material_id, work_order_id, type(in/out), qty, created_at |
| users | 用户 | id, username, password_hash, role |
两个字段设计的细节值得说。第一,所有业务编号如 work_order_no、product_code、material_code 都要建唯一索引,业务操作按编号查找,不要按自增主键找。第二,hard-delete 禁止,全部逻辑删除,生产中一张工单报错了要能追溯,物理删数据在工厂系统里是禁忌。
4.2 后端分层与参数校验
后端我用的 Express,路由层只做参数接收和校验,业务逻辑放到 service 层,数据库访问放到 dal 层。别把业务逻辑堆在路由回调里,后面改起来会非常痛苦。
参数校验用 Joi,每个接口定义 schema。比如报工接口的校验:
const reportSchema = Joi.object({ quantity: Joi.number().integer().min(1).required(), defectiveQty: Joi.number().integer().min(0).max(Joi.ref('quantity')).default(0), });这里有个容易忽略的点:defectiveQty 不应该超过本次报工数量 quantity,如果不校验,一次手误就能把不良率刷成 200%。机器校验看起来只是一个小约束,但实际维护数据质量最有效的就是这类小约束。
4.3 乐观锁解决报工冲突
车间报工最典型的并发问题是两三个人同时给同一张工单报工。比如 A 工人扫了工单号输入 50 件,B 工人也同时报 30 件,如果代码只是先查后改再写回,最后数据库里 completed_qty 可能只是最后一次写入的数值,而不是 80 件。
解决方案是用乐观锁。给 work_orders 表加一个 version 字段,每次更新都带版本号:
UPDATE work_orders SET completed_qty = completed_qty + 50, version = version + 1 WHERE id = 123 AND version = 5;如果影响行数是 0,说明版本过期,接口直接返回“页面数据已变更,请刷新后重试”。车间环境里这种提示比后台去悄悄合并数据更可靠,因为工人能立刻发现别人也在报工,避免漏报。
4.4 权限模型与登录会话
工厂系统权限不用做得很复杂,角色就四种:管理员、计划员、车间主任、工人。工人登录后只能看到自己工序的工单和报工入口,计划员能看全部计划和工单,管理员管基础数据和用户配置。
密码用 bcrypt 加盐哈希保存,绝对不能存明文。前端用 Vue Router 路由守卫做页面级权限判断,后端每个接口再用中间件做角色拦截,两层一起,缺一不可。登录态我用的是 JWT,但会在第 6 节专门说它的坑。
5. 前端页面怎么做到让车间工人愿意用
5.1 页面结构与组件划分
前端项目按菜单拆模块:计划管理、工单管理、物料管理、进度看板、基础数据、系统设置。
计划管理页面是计划员每天打开的第一个页面。左侧列出未排程订单,中间是甘特图排程区域,右侧显示设备占用情况。这个页面我拆成三个组件:PlanGantt.vue 负责甘特图渲染,OrderListPanel.vue 负责待排订单列表,DeviceTimeline.vue 负责设备时间轴。
工单管理页面是车间主任的主战场,列表用卡片而不是表格,因为卡片可以放大显示工单号、产品、数量、状态,一眼扫过去就能找到自己要跟的工单。卡片组件是 WorkOrderCard.vue,状态变化后用动画高亮提示,避免车间主任翻半天看不到新增工单。
5.2 车间平板端的大字体交互
系统要给车间配平板或者工控机,交互设计和办公室完全不一样。工人戴着手套、车间光线差、站着操作,按钮必须够大,文字不能太小。报工页面我把设计模式定为“信息最小化”:屏幕上只显示当前工单的产品、规格、计划数量、已完成数量,下面三个大按钮,“完工 N 件”“不良 N 件”“结束”。
数量输入我用的是数字键盘弹层,不用原生软键盘,因为原生的键盘在平板上会把页面顶上去,体验很差。而且尽量减少点击次数,能扫码就扫码,产品编码和工单号都不允许手动输入,防止输错一个字符找半天。
5.3 看板可视化:不是炫技,是直观
车间看板用的是 ECharts,但真正生效的不是图表样式,而是指标选择。我只保留三个维度:计划完成率、设备利用率、不良率趋势。计划完成率用仪表盘和进度条展示,设备利用率用堆叠柱状图按设备分组,不良率趋势用折线图看最近 14 天走向。
看板的数据通过 Socket.io 推送,服务端在每次报工事件后广播最新数据,前端订阅后更新对应组件。这个链路不复杂,但要注意做好断线重连,车间网络有时候不稳定,WebSocket 掉了要自动重连并拉取一次全量数据兜底。
5.4 移动端和外部系统衔接
车间里还有一类操作需要移动端,比如仓库盘点、车间主任现场巡检。我在架构里预留了一个 H5 的轻量入口,同一个 Vue 项目里通过路由区分,手机扫码打开后进入移动布局。
另外实际情况里,很多工厂已经在用企业微信沟通,可以和现有企业微信账号体系打通,做完单点登录后,工人不需要记住密码,直接在企业微信里打开应用就能看到自己名下待办工单。项目里也把关键事件,比如缺料提醒、工单下发通知,通过企业微信消息推给相关人员,实测下来比短信通知成本低而且触达率高。腾讯地图这类能力在配送场景更常用,这里用不上就不过度设计了。
6. 上线落地阶段最扎心的那些坑
6.1 服务器 Node.js 版本和系统库不匹配
项目上线时遇到最无语的一个问题,是 Linux 服务器上 Node.js 直接启动失败。本地开发用的 Node 18 好好的,但服务器是 CentOS 7.9,系统 glibc 版本偏旧,Node.js 18 的官方二进制需要较新的 GLIBC,结果一启动就报错。
解法是用 nvm 安装与系统匹配的版本,同时确认系统的运行库:
ldd --version nvm install 18.20.4 nvm use 18.20.4后来我学到的教训是,生产环境的 Node.js 版本必须在项目文档里固定,并且用 .nvmrc 或 volta 锁版本。团队里任何一个人本地升大版本,都可能出现“本地正常、服务器跑不了”的诡异问题。
6.2 前后端分离的跨域和 Nginx 配置
开发环境用 Vite 的 proxy 解决跨域,简单省事:
// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } }生产环境我直接用 Nginx 反向代理,让前端页面和后端接口看起来同源,不要去用 CORS 做一堆白名单配置,同源之后 cookie 和 JWT 的处理都干净很多。
location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这里有个细节,WebSocket 也要走同一个 Nginx,必须单独配置 Upgrade 和 Connection 头,否则看板推送在生产环境会失效。
6.3 JWT 过期与车间工人的“长期在线”需求
JWT 最大的坑是过期问题。车间工人早上登录一次,经常挂着到下班,如果 token 只有两小时有效期,下午就突然没法报工了。又不能让他反复输入密码,工人在机台旁边停下来找管理员重置密码,是灾难。
我的做法是双 token 方案:短期 access token 两小时过期,长期 refresh token 七天有效。axios 响应拦截器里判断 401,自动用 refresh token 换新 token,对工人完全无感。
service.interceptors.response.use( res => res, async (error) => { const original = error.config; if (error.response?.status === 401 && !original._retry) { original._retry = true; await refreshToken(); return service(original); } return Promise.reject(error); } );这个方案要考虑多标签页同时抢刷新令牌的问题,我当时做了一个简单的并发锁,确保多个请求同时 401 时只发一次刷新请求,避免 token 被反复刷新导致后续全部失效。
6.4 扫码枪输入法干扰与批量报工
扫码枪本质上就是键盘,扫出来的字符串以键盘事件形式输入,最后再补一个回车。问题在于部分国产输入法在中文状态下会拦截扫码内容,本来应该扫出“WO20240515001”,结果变成一段乱码进到输入框。
解决办法是使用全局监听 keypress 事件拼接缓冲区,不走输入框的普通 keydown 路径:
let buffer = ''; let lastTime = Date.now(); window.addEventListener('keypress', (e) => { buffer += e.key; if (Date.now() - lastTime > 100) buffer = ''; lastTime = Date.now(); if (e.key === 'Enter') { handleScan(buffer.trim()); buffer = ''; e.preventDefault(); } });注意加一个时间判断,防止工人手工敲键盘也被误判成扫码。扫码设备录入的连续按键间隔通常小于 30ms,手工输入很难做到那么快,时间阈值可以调。
最后还有一个容易被忽略的问题:同一张工单,多人同时扫码报工。所以我在报工接口上做了防重复提交,同一个工单同一个用户 5 秒内重复提交会被忽略,前端也会弹个小提示,告诉工人“刚才已经提交过了”。
如果让我重新做一遍这个系统,我会先做“计划 - 工单 - 报工 - 看板”这条主线,跑顺了再往两边加物料、设备和成本。给车间主任留一个 Excel 导出功能特别重要,它不是妥协,而是它给不愿意马上切换到系统的人一个缓冲通道;等数据准确到大家开始依赖看板了,Excel 导出自然就没那么多人用了。业务编号规则也要提前设计好,别用自增 ID 做单号,这个决策越早做,后面打印条码、跨部门沟通、排查问题的时候就越省心。