如何在Node-RED中实现网页内容及节点内容的自动动态更新?
Node-RED节点内容动态更新实现方案
问题分析
当前流程里,Template节点渲染的{{payload.content.time}}和{{payload.content.variable}}是页面加载时的静态值。虽然前端用setInterval每秒执行更新函数,但函数里的这两个值不会自动从后端获取新数据,只会重复显示页面初始化时的固定值,所以必须手动刷新页面才能更新。
解决方案一:前端AJAX定时请求后端接口(改动小,适配现有流程)
1. 新增实时数据接口
在现有流程中添加一组节点,专门返回最新生成的数值:
HTTP In节点:设置URL为/testcase/data,请求方法选GETFunction节点(命名为「获取实时数据」):编写代码从flow存储读取最新值并返回:
var state = {}; state["content"] = { "time": new Date().getSeconds(), "variable": flow.get("variable") || 0 }; msg.payload = state; return msg;
- 连接
HTTP In→Function→HTTP Response节点
2. 修改Template节点的前端代码
更新Template中的JavaScript部分,用fetch定时请求新接口获取实时数据:
<script> function updateDynamicContent() { var dynamicContent = document.getElementById('window'); var dynamicContent2 = document.getElementById('window2'); var dynamicContent3 = document.getElementById('window3'); // 更新本地时间 var currentTime = new Date(); dynamicContent.innerHTML = currentTime.getSeconds(); // 请求后端实时数据 fetch('/testcase/data') .then(response => response.json()) .then(data => { dynamicContent2.innerHTML = data.content.time; dynamicContent3.innerHTML = data.content.variable; }) .catch(error => console.error('获取数据失败:', error)); } // 每秒执行一次更新 setInterval(updateDynamicContent, 1000); </script>
3. 优化原Function节点逻辑
确保原「Generate content」节点能持续更新flow存储的数值,并且无论请求来自HTTP还是inject都返回消息:
var date = new Date(); var inject = msg.payload; // 生成数值并存入flow存储 flow.set("variable", Math.round(10*Math.sin(date.getSeconds()))); var state = {}; state["content"] = { "time": date.getSeconds(), "variable": flow.get("variable"), "inject": inject }; msg.payload = state; return msg;
完整流程代码
[{"id":"d67de8615ea3d3a0","type":"tab","label":"Flow 2","disabled":false,"info":"","env":[]},{"id":"9777c31bb0ed4db4","type":"http response","z":"d67de8615ea3d3a0","name":"","statusCode":"200","headers":{},"x":760,"y":160,"wires":[]},{"id":"ad9334b7892cf503","type":"function","z":"d67de8615ea3d3a0","name":"Generate content","func":"var date = new Date();\nvar inject = msg.payload;\n\n// generate some value and save it in the flow storage\nflow.set(\"variable\", Math.round(10*Math.sin(date.getSeconds())));\n\n// create some JSON \nvar state = {};\nstate[\"content\"] = {\n \"time\": date.getSeconds(),\n \"variable\": flow.get(\"variable\"),\n \"inject\": inject\n};\n// handle it to a message payload\nmsg.payload = state;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":160,"wires":[["652219a15a6b71e9"]]},{"id":"652219a15a6b71e9","type":"template","z":"d67de8615ea3d3a0","name":"HTML","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <style>\n /* Center the window both vertically and horizontally */\n body {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background-color: lightgrey; /* Background color of the page */\n }\n\n /* Style for the window */\n #window {\n width: 200px;\n height: 100px;\n background-color: lightblue; /* Blue color for the window */\n border: 5px solid black; /* Black border */\n border-radius: 10px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 36px;\n font-weight: bold;\n color: red; /* Red color for the text */\n }\n #window2 {\n width: 200px;\n height: 100px;\n background-color: lightblue; /* Blue color for the window */\n border: 5px solid black; /* Black border */\n border-radius: 10px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 36px;\n font-weight: bold;\n color: red; /* Red color for the text */\n }\n #window3 {\n width: 200px;\n height: 100px;\n background-color: lightblue; /* Blue color for the window */\n border: 5px solid black; /* Black border */\n border-radius: 10px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 36px;\n font-weight: bold;\n color: red; /* Red color for the text */\n }\n </style>\n</head>\n<body>\n <div id=\"window\">\n <span>var1</span> <!-- The var1 inside the window -->\n </div>\n <div id=\"window2\">\n <span>var2</span> <!-- The var2 inside the window -->\n </div>\n <div id=\"window3\">\n <span>var3</span> <!-- The var3 inside the window -->\n </div>\n</body>\n<script>\n function updateDynamicContent() {\n var dynamicContent = document.getElementById('window');\n var dynamicContent2 = document.getElementById('window2');\n var dynamicContent3 = document.getElementById('window3');\n\n var currentTime = new Date();\n \n dynamicContent.innerHTML = currentTime.getSeconds();\n \n // 请求后端实时数据\n fetch('/testcase/data')\n .then(response => response.json())\n .then(data => {\n dynamicContent2.innerHTML = data.content.time;\n dynamicContent3.innerHTML = data.content.variable;\n })\n .catch(err => console.log('数据获取失败:', err));\n\t}\n\n\t// Use setInterval to call the updateDynamicContent function every second\n\tsetInterval(updateDynamicContent, 1000);\n</script>\n</html>\n\n","output":"str","x":570,"y":160,"wires":[["9777c31bb0ed4db4"]]},{"id":"098d5034ca668638","type":"inject","z":"d67de8615ea3d3a0","name":"","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":false,"onceDelay":"1","topic":"","payload":"true","payloadType":"bool","x":130,"y":240,"wires":[["ad9334b7892cf503"]]},{"id":"0a6681231f307f16","type":"http in","z":"d67de8615ea3d3a0","name":"","url":"/testcase","method":"get","upload":false,"swaggerDoc":"","x":150,"y":160,"wires":[["ad9334b7892cf503"]]},{"id":"a1b2c3d4e5f6g7h8","type":"http in","z":"d67de8615ea3d3a0","name":"","url":"/testcase/data","method":"get","upload":false,"swaggerDoc":"","x":150,"y":320,"wires":[["b9c8d7e6f5a4b3c2"]]},{"id":"b9c8d7e6f5a4b3c2","type":"function","z":"d67de8615ea3d3a0","name":"Get Real-time Data","func":"var state = {};\nstate[\"content\"] = {\n \"time\": new Date().getSeconds(),\n \"variable\": flow.get(\"variable\") || 0\n};\nmsg.payload = state;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":320,"wires":[["9777c31bb0ed4db4"]]}]
解决方案二:WebSocket实时推送(低延迟,主动更新)
如果需要更低延迟的实时更新,可以用Node-RED的WebSocket节点实现后端主动推送:
- 添加
websocket out节点,设置路径为/ws/testcase - 将原「Generate content」节点的输出连接到
websocket out节点,让后端每秒推送最新数值 - 修改Template中的前端代码,用WebSocket接收推送数据:
// 建立WebSocket连接 const ws = new WebSocket(`ws://${window.location.host}/ws/testcase`); ws.onmessage = function(event) { const data = JSON.parse(event.data); document.getElementById('window2').innerHTML = data.content.time; document.getElementById('window3').innerHTML = data.content.variable; }; // 本地时间仍用定时更新 setInterval(() => { document.getElementById('window').innerHTML = new Date().getSeconds(); }, 1000);
这种方式无需前端定时请求,后端数值更新时直接推送给页面,效率更高。
内容的提问来源于stack exchange,提问作者Arseniy Sleptsov
相关产品推荐
相关产品推荐

