
简介这是一款基于原生JavaScript开发的轻量级组态软件面向电力、工业自动化及物联网场景适合需要快速搭建电力一次接线图、工业流程图等图形界面的前端开发者。软件不依赖后台框架由服务端按约定数据格式返回即可驱动具备拖拉拽编辑能力灵活性较高。资源包共1013个文件约14.4MB包含大量svg、png、gif图形素材js源码与json数据示例还附带html、css、字体文件及说明文档便于二次开发与界面定制。目前已有190人学习下载。通过该包可掌握无框架组态工具的实现思路熟悉从图形拖拽到数据绑定的完整流程同时获得一套可直接扩展的电力行业图形素材与项目骨架适合熟悉JavaScript的开发者用于学习和实战。 我最早是在一个工控交流群里看到这个需求的对方发了一句话“采用原生的js实现的组态软件没有后台后台需要根据数据格式返回或者联系作者私发。”底下跟着好几张运行截图画面里是设备流程图、阀门状态、实时曲线。当时我就觉得这东西挺有意思——一个纯前端的组态软件没有自己的服务端所有数据都要后台按约定的格式吐给前端。说白了它就是一个跑在浏览器里的HMI/SCADA编辑器加运行环境拖拖拽拽把水泵、阀门、管道画出来保存成工程文件运行时拿到后台推送的数据让画面上的设备跟着数据动态变色、旋转、报警。适合谁用想给自己的设备监控系统快速加一套可视化界面的嵌入式工程师、做物联网平台的前端开发、还有那些被商业组态软件授权费搞得头疼的小项目负责人都可以参考这条技术路线。这篇文章我就把这个项目从架构选型到数据对接的完整思路拆开讲重点是数据格式怎么设计、前后台怎么配合、以及原生JS在这种场景下的真实优势和坑。1. 为什么用原生JS做组态软件没有后台反而是优势1.1 项目定位与架构逻辑大多数人对组态软件的第一印象是WinCC、组态王这类桌面软件装起来好几个G授权贵还得配Windows工控机。而这个项目反过来它把“组态”这件事完全塞进了浏览器里而且用的是原生JavaScript不是Vue、不是React连构建工具都没上。很多人一看“原生JS”就觉得是不是为了炫技实际做下来你会发现这是非常务实的选型。组态软件的运行环境是工控现场那里有很多老旧的Windows系统浏览器可能是Chrome 49也可能是IE11的壳。你用Vue3写一套光运行时polyfill就够折腾的。原生JS解析、渲染、事件绑定自己控制兼容性完全握在自己手里。而且整个工程就是几个JS文件扔到任意静态服务器上就能用甚至双击HTML文件都能打开。没有后台这个设计我一开始也觉得奇怪仔细想就明白了组态软件本质上是数据消费方真正的数据源是PLC、DTU、MES系统这些已经有现成的数据服务了。再造一个后台出来等于在中间多隔了一层反而增加延迟和维护成本。所以这个项目只做一件事把数据用约定的格式吐出来谁来吐都行MES系统可以、Node-RED可以、一个简单的SpringBoot服务也可以。1.2 这套方案解决什么核心痛点从实际落地场景倒推这套方案最打动人的是三点。部署成本极低。你不需要装数据库不需要跑Java服务不用配.NET环境生产环境只要一个能放静态文件的Nginx就够了。对于那种需要一个可视化监控大屏、又没有预算上商业组态软件的小型项目来说这个优势是决定性的。修改画面不用停机。传统组态软件改画面得在开发环境改了再编译下发这套纯前端的方案改起来就是编辑JSON或者重画SVG刷新浏览器就生效。现场调试的时候这个体验真的香。协议对接天然开放。因为没后台所以数据对接就是“你按我的JSON格式发数据”至于数据从哪来、怎么采集作者完全不关心。这就给了集成方极大的自由度你就是用串口读卡器写个数据转发脚本都能接进来。2. 数据格式设计前后台之间的“共同语言”2.1 点位模型与数据结构组态软件里的每一个动态元素本质上都在消费一个“点位”。在实际项目中点位通常用这种结构来表达。{ pointId: sensor_pump_001, name: 1号水泵出口压力, value: 0.42, quality: 0, timestamp: 1736387280000, type: pressure }这个结构基本参考了传统工控系统的点位标准。quality字段很关键0表示正常1表示数据超时2表示设备断线。为什么要把质量戳单独拎出来因为现场数据不是永远可靠的PLC离线、网线松动、传感器开路这些都要在画面上有反馈。如果只传value你根本分不清“压力为0”是正常停机还是传感器坏了。整体数据组织建议采用“工程-页面-点位”的三级结构。工程有一个唯一标识页面列表指明当前显示哪个画面点位字典则包含这个页面所有用到的点。前端加载时先拿全量点位快照建立映射再订阅增量更新。2.2 快照数据与增量数据先说全量快照它的作用是让画面在打开的一瞬间就能把当前所有状态显示出来。采集端会把当前所有点位的值一次性推送过来前端拿到后逐点位做初始化渲染。快照数据按页面维度拆分更合理不然画面多了数据量会很大。增量数据是运行时的常态后台每个刷新周期只推送变化的数据。最小单元是一个点位也可以打包成一组点位一起推送。前端收到增量后会走一遍“点位映射-属性更新-重绘”的流程。在实际绘制时一个点位可能绑定多个图元属性。比如一个温度点既可以控制一个数字显示框的文本也可以控制一个温度计图标的填充高度还可以决定一个指示灯的颜色。所以前端的数据引擎里会维护一张绑定关系表格式类似pointId、deviceId、attribute、expression。这样设计的好处是数据层和呈现层解耦后面加新属性不需要改数据引擎。2.3 一个可以直接用的JSON通信约定结合多个类似项目的实践经验我整理了一套可以直接套用的默认报文规范按type区分消息类型。// 快照请求页面加载后前端主动发起 { requestId: snapshot_001, type: snapshot, projectId: proj_01, pageId: page_main } // 快照响应后台返回当前页面所有点位 { type: snapshot_ack, requestId: snapshot_001, points: [ { pointId: p1, value: 1.0, quality: 0, timestamp: 1736387280000 } ] } // 增量推送后台定时或变化时推送 { type: point_update, projectId: proj_01, points: [ { pointId: p1, value: 0.85, quality: 0, timestamp: 1736387290000 } ] }这个约定有几个细节需要注意。requestId用于拉流时的请求关联防止异步返回时对不上号timestamp必须用毫秒时间戳前端要做时间轴回放或者曲线展示时缺了它就很难对齐点在增量报文中必须传全量字段不要省掉quality和timestamp否则前端如果要刷新质量状态就还得发一次快照请求。3. 原生JS的实现机制与核心渲染原理3.1 渲染方案选择Canvas还是SVG组态画面里既有大量基础图元也有复杂的设备图形选错渲染方案后面很头疼。这个项目用的方案是用SVG做静态设备图用Canvas做实时数据曲线和动画层两者叠加。SVG的优势是DOM化每个图元都是独立元素能被选中、能直接绑定事件、能改fill属性变颜色。这对组态编辑器的“拖拽-选中-属性编辑”操作来说简直天作之合。你不需要自己实现命中检测浏览器本身就是最好的图形交互框架。但SVG的短板也很明显图元数量超过500个以后操作会明显卡顿。Canvas适合高频变化的区域。实时曲线每秒钟刷新几十次如果用SVG的话就是对DOM的疯狂操作而Canvas只重绘一片区域性能差了一个量级。简单总结静态图、可交互、需要编辑的用SVG高频刷新、大量粒子、流畅动画用Canvas小图标、状态灯、小面积控件直接用SVG反而省事3.2 数据订阅与界面刷新机制原生JS实现数据驱动的界面刷新核心是发布-订阅模式。组态运行时会建立一个数据总线各UI组件向总线订阅自己关心的点位点位数据更新后总线批量派发事件。const DataBus { topics: {}, subscribe: function(pointId, callback) { if (!this.topics[pointId]) this.topics[pointId] []; this.topics[pointId].push(callback); }, publish: function(pointId, data) { const subs this.topics[pointId] || []; for (const fn of subs) { fn(data); } } };这里有个性能细节容易被忽略不要把数据变更直接同步触发DOM操作而是在publish时先把变化记录到一个脏数据队列然后通过requestAnimationFrame在下一帧统一刷新。这样在极端情况下一帧内收到10个点位更新也只触发一次重绘性能会平滑很多。3.3 图元的绑定与表达式组态软件里图元的属性不应该是写死的要支持绑定表达式。举个例子一个电机图形它的旋转角度可能绑定到一个频率点位“当频率大于30时旋转大于50时加速”。这种逻辑在代码里就是一个表达式字符串类似这样// 绑定表达式由用户配置运行时通过Function构造器动态求值 const expr value 50 ? 120 : value 30 ? 60 : 0; const speed new Function(value, return expr)(pointValue);允许Function构造器执行动态代码安全性上要认真考量。工程文件如果来自不可信来源就会被注入恶意脚本。实际做的时候需要在导入工程文件时做一次安全扫描只允许白名单函数和算术运算符不允许出现script标签、XMLHttpRequest、fetch、eval等敏感关键字。4. 后台对接实操三种数据返回方式4.1 没有后台数据从哪来这是这个项目被问得最多的问题“你没有后台那我的数据怎么显示上去”你要把思路转化一下这里的“没有后台”是指项目不自带后台但运行环境一定有一个数据提供方。只是这个数据提供方不需要跟这个项目绑定部署它可以是你服务器上已有的采集程序也可以是第三方云平台的消息推送。前端的接入方式就按后台实际能提供的通道来选通常有三种。4.2 HTTP定时拉取方式最省事的方式后台只需要提供一个HTTP接口返回JSON。前端用setInterval定时轮询每1到3秒拉一次。async function pollData() { try { const res await fetch(http://your-host/api/realtime); const data await res.json(); handleIncrement(data); } catch (e) { console.error(poll error, e); } } setInterval(pollData, 2000);这种方式优点是后台实现成本几乎为零任何一种后端语言都能轻松做到。缺点是无法精准推送数据变化及时的会滞后最多一个轮询周期。真实场景里设备状态变化后2秒内能显示出来大部分监控需求都能接受了。4.3 WebSocket与MQTT方式适合要求毫秒级响应的场景比如阀门开关联锁、设备急停状态。WebSocket是浏览器原生支持的长连接通道前端可以复用已有的消息解析逻辑只是把数据来源从fetch换成ws.onmessage。MQTT稍微特殊它是物联网场景的事实标准但浏览器不能直接以TCP方式连接MQTT Broker需要一个WebSocket网关做桥接。EMQX这类Broker原生支持WebSocket端口前端用MQTT.js这个库就可以直接订阅主题了。采用MQTT方式后后台采集程序只需要负责把采集到的数据publish到指定主题前端subscribe同一个主题整个链路就是标准的物联网数据流。这种方式在大规模接入场景下明显更优雅因为它天然支持多个客户端同时订阅而且Broker具有消息缓存能力前端断线重连后能快速拿到最新状态。4.4 老系统接入的兼容方案有些现场是跑了几十年的老系统数据库是SQL Server 2000接口是COM组件你根本不可能让它直接吐JSON。这种情况最终实践下来最稳妥的办法是加一个微小的数据转换服务它读取老系统的数据源然后按约定的JSON格式推送给前端。数据转换服务放一台工控机上跑就行代码量在一两百行左右。转换服务内部逻辑很简单老数据源读取全部点位转换成标准点位格式按指定的数据通道推送给前端。谁承担转换谁就负责协议适配这样前端永远只面对一套标准数据格式不用关心后台的数据库是什么、接口是SOAP还是MODBUS。这也是为什么作者强调“后台需要根据数据格式返回”——因为这套前端已经帮你把复杂的图形渲染、联动、报警都做完了唯一的要求就是你给它喂符合规范的数据。4.5 跨域问题与本地存储纯前端应用对接接口时跨域问题十有八九会遇到。简单粗暴的办法是在后端加CORS响应头。但如果后台不在自己手里可以考虑用Nginx反向代理转发把API路径代理到后台真实地址前端始终访问同源地址就绕开了跨域限制。location /api/ { proxy_pass http://192.168.1.100:8080/api/; proxy_set_header Host $host; proxy_read_timeout 3600s; }工程文件的保存也一样前端把画好的组态工程序列化成JSON要么发给后台持久化要么保存到localStorage。做原型演示用localStorage够了真正落地还是建议有一个后台保存接口否则浏览器换一台电脑工程就丢了。5. 我在实际项目中踩过的坑与排查方法5.1 常见问题速查表现象可能原因排查方法画面打开后没有数据快照请求接口路径配置错误按F12看Network是否有请求有请求则看返回的JSON结构是否匹配数据不动不刷新增量推送周期太长或通道被断开在后台打印增量报文确认是否按格式推送点位值正常但不显示点位ID和图元绑定的点位ID不一致导出工程JSON逐一对比绑定关系曲线偶尔跳点时间戳格式不一致统一使用毫秒时间戳避免秒和毫秒混用画面卡顿SVG图元过多或刷新频率过高减少全页面重绘区域用Canvas接管高频变动部分字段值超界显示异常范围配置越界图元属性里设置量程上限超出后自动限幅5.2 几个值得说道的实战教训第一件是增量数据不要带整个页面全部点位。我见过有后台同学图省事每次都把上千个点位的值全量推送过来。前端虽然解析没压力但每次全量更新会触发所有图元的重绘画面肉眼可见地掉帧。增量推送就只推送变化的点位这才是组态软件该有的交互节拍。第二件是注意字符串精度。工控场景中小数的精度很关键比如压力表显示三位小数配置里如果约束不严格后台传了带五位小数的值显示位会溢出。可以在点位配置里增加decimals字段前端根据它统一做格式化比每个地方单独处理要省心得多。第三件是断线重连必须做。WebSocket连接断开后前端要自动重连并重新拉一次快照。因为断线期间漏掉的增量数据已经无从补起只有用快照来重置状态。如果不做这一步现场网络一抖画面上某些设备就停在旧状态上不动了很容易造成误判。5.3 运行维护期的一些建议画面页面不要画太满一张画面控制在30个设备图元以内最佳。这个数字可能跟宣传截图里那种满屏花花绿绿的感觉有落差但实际监控场景反而讲究清爽。设备太多操作员根本盯不过来也容易掩盖真正需要关注的告警。工程文件要养成版本管理的习惯。组态软件改画面很频繁今天加个设备、明天调个颜色没有版本控制就等着哭。好在工程是JSON文本天然适合Git管理每改一版提交一次改坏了随时回滚比传统组态软件那个专有工程格式好用太多了。6. 项目扩展方向与二次开发思路这个组态软件做完基础功能后我建议往这几个方向扩展性价比很高。引入通用SVG图库合集。工控界的设备图形翻来覆去就是电机、阀门、泵、管道、储罐、仪表盘这些有了一套通用图库新产品做画面时就不用从零画了。搜索“工控组态软件通用SVG图库合集”能找到很多现成资源核心是整理出来按行业分类比如水处理行业一套、光伏行业一套。对接GeoJSON做GIS总览层。如果监控对象是分布式的比如分布在几个城市的污水处理站可以在地图上用GeoJSON画站点分布点击站点下钻到该站的组态画面。这个扩展本质上是把组态软件从单画面扩展成多层级联从总览地图到单设备详情一层层钻取。实现方式也很直接地图用Leaflet轻量集成GeoJSON作为底图数据弹窗里嵌入已有的组态页面。支持报警联动与语音播报。报警在工控场景中是刚需不仅仅是弹窗提示更要做到现场声光报警联动。前端可以监听点位质量戳变化告警时触发页面闪烁、弹窗、TTS语音播报甚至通过WebSocket给后端发一条指令让现场声光报警器动作。这些用原生JS都能实现不需要引入重量级框架。说实话原生JS做组态软件这个路线真正吸引我的是那种“一切尽在掌握”的感觉。没有框架的黑魔法没有打包器在你不知道的地方算了一个你不知道的hash整个项目就是清晰的文件结构、直接的DOM操作、可控的性能表现。当然它也意味着一切都要自己写没有现成的组件库可以抄。但正是这种朴素让它在工控现场那种复杂环境里特别皮实。我个人的体会是如果你只是做一个内部工具、一套设备监控界面从这套思路出发去定制比花大价钱买商业组态授权或者杀鸡用牛刀地上一套重型前端框架都要划算得多。本文还有配套的精品资源点击获取